From 749124f834c30bb12021718d7389923d9efbbab0 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Mon, 15 Jun 2026 20:11:36 -0400 Subject: [PATCH 01/53] api groundwork (#1648) --- Cargo.lock | 9 ++ Cargo.toml | 2 + .../artcraft/artcraft_api_keys/Cargo.toml | 20 +++ .../artcraft/artcraft_api_keys/src/lib.rs | 104 +++++++++++++ .../src/parse_from_header_value.rs | 125 +++++++++++++++ .../schema/database/mysql_queries/Cargo.toml | 1 + .../src/queries/api_keys/insert_api_key.rs | 5 +- crates/service/web/storyteller_web/Cargo.toml | 1 + .../api_keys/create_api_key_handler.rs | 4 +- .../get_authorization_header_api_key.rs | 144 ++++++++++++++++++ .../src/http_server/web_utils/mod.rs | 1 + .../src/util/generate_api_key.rs | 23 +-- 12 files changed, 427 insertions(+), 12 deletions(-) create mode 100644 crates/api_clients/artcraft/artcraft_api_keys/Cargo.toml create mode 100644 crates/api_clients/artcraft/artcraft_api_keys/src/lib.rs create mode 100644 crates/api_clients/artcraft/artcraft_api_keys/src/parse_from_header_value.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/web_utils/get_authorization_header_api_key.rs diff --git a/Cargo.lock b/Cargo.lock index 159bd14281..2aa2da3463 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -712,6 +712,13 @@ dependencies = [ "workspace-build-acceleration", ] +[[package]] +name = "artcraft_api_keys" +version = "0.0.1" +dependencies = [ + "workspace-build-acceleration", +] + [[package]] name = "artcraft_client" version = "0.0.1" @@ -7297,6 +7304,7 @@ name = "mysql_queries" version = "0.0.1" dependencies = [ "anyhow", + "artcraft_api_keys", "bucket_paths", "chrono", "composite_identifiers", @@ -11556,6 +11564,7 @@ dependencies = [ "actix_helpers", "anyhow", "artcraft_api_defs", + "artcraft_api_keys", "artcraft_router", "async-stripe 0.26.0", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index fad9ecea3d..112fcf3365 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ # API clients for external services # These might be custom implementations or wrappers over other client libraries. "crates/api_clients/artcraft/artcraft_api_defs", + "crates/api_clients/artcraft/artcraft_api_keys", "crates/api_clients/artcraft/artcraft_client", "crates/api_clients/artcraft/artcraft_router", "crates/api_clients/cloud_storage", @@ -232,6 +233,7 @@ workspace-build-acceleration = { path = "crates/workspace-build-acceleration" } actix_artcraft = { path = "crates/lib/actix_artcraft" } actix_helpers = { path = "crates/lib/actix_helpers" } artcraft_api_defs = { path = "crates/api_clients/artcraft/artcraft_api_defs" } +artcraft_api_keys = { path = "crates/api_clients/artcraft/artcraft_api_keys" } artcraft_client = { path = "crates/api_clients/artcraft/artcraft_client" } artcraft_router = { path = "crates/api_clients/artcraft/artcraft_router" } bootstrap = { path = "crates/lib/bootstrap" } diff --git a/crates/api_clients/artcraft/artcraft_api_keys/Cargo.toml b/crates/api_clients/artcraft/artcraft_api_keys/Cargo.toml new file mode 100644 index 0000000000..6d94127f5d --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_api_keys/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "artcraft_api_keys" +edition = "2024" +version = "0.0.1" +authors = [ + "Brandon Thomas ", + "Brandon Thomas ", +] +publish = false + +[package.metadata.cargo-udeps.ignore] +normal = ["workspace-build-acceleration"] + +[lib] +name = "artcraft_api_keys" +path = "src/lib.rs" + +[dependencies] +# Hackari workspace (faster compile times) +workspace-build-acceleration.workspace = true diff --git a/crates/api_clients/artcraft/artcraft_api_keys/src/lib.rs b/crates/api_clients/artcraft/artcraft_api_keys/src/lib.rs new file mode 100644 index 0000000000..05d290fa25 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_api_keys/src/lib.rs @@ -0,0 +1,104 @@ +use std::fmt; + +use crate::parse_from_header_value::parse_from_header_value; + +pub mod parse_from_header_value; + +/// Number of leading characters shown when an [`ArtcraftApiKey`] is formatted. The remainder of +/// the secret is redacted so the full key never lands in logs or error messages. +const REDACTED_PREFIX_CHAR_COUNT: usize = 20; + +/// An Artcraft API key (e.g. `artcraft_api_`). +/// +/// `Debug` and `Display` both redact the value, printing only the first +/// [`REDACTED_PREFIX_CHAR_COUNT`] characters followed by an ellipsis so the full secret is never +/// accidentally logged. +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct ArtcraftApiKey(pub String); + +impl ArtcraftApiKey { + pub fn new_from_str(value: &str) -> ArtcraftApiKey { + ArtcraftApiKey(value.to_string()) + } + + /// Parse an [`ArtcraftApiKey`] from the value of an HTTP `Authorization` header, accepting the + /// `Bearer `, `Key `, and bare `` forms. See [`parse_from_header_value`] for the + /// full parsing rules. + pub fn parse_from_authorization_header_value(header_value: &str) -> Option { + parse_from_header_value(header_value) + } + + /// The full, unredacted key value as a borrowed `&str`. + /// + /// NB: unlike `Display`/`Debug` (which redact), this exposes the complete secret — the + /// `_be_careful` suffix is a reminder to use it only where the real value is genuinely needed + /// (storage, the create-key response, tests), never in logs or error messages. + pub fn as_str_be_careful(&self) -> &str { + &self.0 + } + + /// The full, unredacted key value as an owned `String`. See [`Self::as_str_be_careful`] for the + /// caveat behind the `_be_careful` suffix. + pub fn to_string_be_careful(&self) -> String { + self.0.clone() + } + + /// The redacted form used by both `Debug` and `Display`: the first + /// [`REDACTED_PREFIX_CHAR_COUNT`] characters followed by an ellipsis. + fn redacted(&self) -> String { + let prefix: String = self.0.chars().take(REDACTED_PREFIX_CHAR_COUNT).collect(); + format!("{prefix}…") + } +} + +impl fmt::Display for ArtcraftApiKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.redacted()) + } +} + +impl fmt::Debug for ArtcraftApiKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "ArtcraftApiKey({:?})", self.redacted()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const SAMPLE_KEY: &str = "artcraft_api_55ax0zhd580m598r6n4n7szdwjb2b28sypapvawh"; + + #[test] + fn new_from_str_and_as_str_round_trip() { + let key = ArtcraftApiKey::new_from_str(SAMPLE_KEY); + assert_eq!(key.as_str_be_careful(), SAMPLE_KEY); + assert_eq!(key.0, SAMPLE_KEY); + } + + #[test] + fn display_redacts_to_first_twenty_characters() { + let key = ArtcraftApiKey::new_from_str(SAMPLE_KEY); + assert_eq!(format!("{key}"), "artcraft_api_55ax0zh…"); + } + + #[test] + fn debug_redacts_to_first_twenty_characters() { + let key = ArtcraftApiKey::new_from_str(SAMPLE_KEY); + assert_eq!(format!("{key:?}"), "ArtcraftApiKey(\"artcraft_api_55ax0zh…\")"); + } + + #[test] + fn formatting_does_not_leak_the_full_secret() { + let key = ArtcraftApiKey::new_from_str(SAMPLE_KEY); + let suffix = &SAMPLE_KEY[REDACTED_PREFIX_CHAR_COUNT..]; + assert!(!format!("{key}").contains(suffix)); + assert!(!format!("{key:?}").contains(suffix)); + } + + #[test] + fn shorter_than_prefix_is_printed_in_full() { + let key = ArtcraftApiKey::new_from_str("abc"); + assert_eq!(format!("{key}"), "abc…"); + } +} diff --git a/crates/api_clients/artcraft/artcraft_api_keys/src/parse_from_header_value.rs b/crates/api_clients/artcraft/artcraft_api_keys/src/parse_from_header_value.rs new file mode 100644 index 0000000000..61bdfce32c --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_api_keys/src/parse_from_header_value.rs @@ -0,0 +1,125 @@ +use crate::ArtcraftApiKey; + +/// `Bearer ` — the widely-accepted style for most APIs. +const BEARER_SCHEME: &str = "bearer"; + +/// `Key ` — legacy GitHub-style / Fal-style. +const KEY_SCHEME: &str = "key"; + +/// Parse an [`ArtcraftApiKey`] from the value of an HTTP `Authorization` header. +/// +/// The value is accepted in three forms: +/// +/// - `Bearer ` (widely-accepted style for most APIs) +/// - `Key ` (legacy GitHub-style / Fal-style) +/// - `` (AWS-style — the bare key, no scheme) +/// +/// The scheme keyword is matched case-insensitively, and surrounding/inner whitespace is trimmed. +/// Returns `None` if the value is empty, carries an unsupported scheme (e.g. `Basic ...`), or names +/// a scheme with no credential. +pub fn parse_from_header_value(header_value: &str) -> Option { + let trimmed = header_value.trim(); + if trimmed.is_empty() { + return None; + } + + let key = match trimmed.split_once(char::is_whitespace) { + // A scheme word followed by a value: "Bearer " / "Key ". + Some((scheme, rest)) => { + if is_supported_scheme(scheme) { + let key = rest.trim(); + if key.is_empty() { + // A supported scheme with no credential. + return None; + } + key + } else { + // An unsupported scheme (e.g. "Basic ..."). A bare API key never + // contains whitespace, so a multi-token value we don't recognize is + // not a usable key. + return None; + } + } + // No whitespace: AWS-style bare API key ("") — unless the value is + // just a bare scheme keyword (a malformed header carrying no credential). + None => { + if is_supported_scheme(trimmed) { + return None; + } + trimmed + } + }; + + Some(ArtcraftApiKey::new_from_str(key)) +} + +fn is_supported_scheme(word: &str) -> bool { + word.eq_ignore_ascii_case(BEARER_SCHEME) || word.eq_ignore_ascii_case(KEY_SCHEME) +} + +#[cfg(test)] +mod tests { + use super::*; + + const SAMPLE_KEY: &str = "artcraft_api_55ax0zhd580m598r6n4n7szdwjb2b28sypapvawh"; + + fn parsed(header_value: &str) -> Option { + parse_from_header_value(header_value).map(|key| key.to_string_be_careful()) + } + + #[test] + fn bearer_scheme_is_case_insensitive() { + assert_eq!(parsed("Bearer abc"), Some("abc".to_string())); + assert_eq!(parsed("bearer abc"), Some("abc".to_string())); + assert_eq!(parsed("BEARER abc"), Some("abc".to_string())); + assert_eq!(parsed("BeArEr abc"), Some("abc".to_string())); + } + + #[test] + fn key_scheme_is_case_insensitive() { + assert_eq!(parsed("Key abc"), Some("abc".to_string())); + assert_eq!(parsed("key abc"), Some("abc".to_string())); + assert_eq!(parsed("KEY abc"), Some("abc".to_string())); + } + + #[test] + fn bare_key_aws_style() { + assert_eq!(parsed("abc"), Some("abc".to_string())); + assert_eq!(parsed(SAMPLE_KEY), Some(SAMPLE_KEY.to_string())); + } + + #[test] + fn surrounding_and_inner_whitespace_is_trimmed() { + assert_eq!(parsed(" Bearer abc "), Some("abc".to_string())); + assert_eq!(parsed("\tKey\tabc\t"), Some("abc".to_string())); + assert_eq!(parsed(" abc "), Some("abc".to_string())); + } + + #[test] + fn empty_or_blank_is_none() { + assert_eq!(parsed(""), None); + assert_eq!(parsed(" "), None); + } + + #[test] + fn scheme_with_no_credential_is_none() { + assert_eq!(parsed("Bearer"), None); + assert_eq!(parsed("Bearer "), None); + assert_eq!(parsed("Key"), None); + assert_eq!(parsed("key "), None); + } + + #[test] + fn unsupported_scheme_is_none() { + assert_eq!(parsed("Basic dXNlcjpwYXNz"), None); + assert_eq!(parsed("Digest abc"), None); + // Two non-scheme tokens — a bare key never contains whitespace. + assert_eq!(parsed("abc def"), None); + } + + #[test] + fn returns_an_artcraft_api_key() { + let key = parse_from_header_value(&format!("Bearer {SAMPLE_KEY}")).expect("should parse"); + assert_eq!(key.as_str_be_careful(), SAMPLE_KEY); + } +} diff --git a/crates/schema/database/mysql_queries/Cargo.toml b/crates/schema/database/mysql_queries/Cargo.toml index 6841c9dbb0..b5184750f8 100644 --- a/crates/schema/database/mysql_queries/Cargo.toml +++ b/crates/schema/database/mysql_queries/Cargo.toml @@ -20,6 +20,7 @@ path = "src/lib.rs" workspace-build-acceleration.workspace = true # Internal +artcraft_api_keys.workspace = true bucket_paths.workspace = true composite_identifiers = { path = "../../../schema/public/composite_identifiers" } enums = { workspace = true, features = ["database"] } diff --git a/crates/schema/database/mysql_queries/src/queries/api_keys/insert_api_key.rs b/crates/schema/database/mysql_queries/src/queries/api_keys/insert_api_key.rs index 30787e42f1..cfd0f95d3e 100644 --- a/crates/schema/database/mysql_queries/src/queries/api_keys/insert_api_key.rs +++ b/crates/schema/database/mysql_queries/src/queries/api_keys/insert_api_key.rs @@ -2,6 +2,7 @@ use std::marker::PhantomData; use sqlx::{Executor, MySql}; +use artcraft_api_keys::ArtcraftApiKey; use tokens::tokens::api_keys::ApiKeyToken; use tokens::tokens::users::UserToken; @@ -13,7 +14,7 @@ where pub ip_address: &'e str, pub name: &'e str, pub maybe_description: Option<&'e str>, - pub api_key: &'e str, + pub api_key: &'e ArtcraftApiKey, pub mysql_executor: E, pub phantom: PhantomData<&'c E>, } @@ -44,7 +45,7 @@ SET created_at = NOW() "#, token.as_str(), - args.api_key, + args.api_key.as_str_be_careful(), // NB: This is okay in this case. args.name, args.maybe_description, args.owner_user_token.as_str(), diff --git a/crates/service/web/storyteller_web/Cargo.toml b/crates/service/web/storyteller_web/Cargo.toml index 0cda8305f8..8863de2412 100644 --- a/crates/service/web/storyteller_web/Cargo.toml +++ b/crates/service/web/storyteller_web/Cargo.toml @@ -36,6 +36,7 @@ actix_artcraft.workspace = true actix_cors_configs = { path = "../../../lib/actix_cors_configs" } actix_helpers.workspace = true artcraft_api_defs.workspace = true +artcraft_api_keys.workspace = true artcraft_router.workspace = true bootstrap.workspace = true bucket_paths.workspace = true diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/create_api_key_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/create_api_key_handler.rs index 4625a6a2bd..191845436b 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/create_api_key_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/create_api_key_handler.rs @@ -86,6 +86,8 @@ pub async fn create_api_key_handler( Ok(Json(CreateApiKeySuccessResponse { success: true, api_key_token: token, - api_key, + // Return the full secret value (never the redacted Debug/Display form). This is the only + // time it is ever exposed. + api_key: api_key.to_string_be_careful(), // NB: This is okay in this case })) } diff --git a/crates/service/web/storyteller_web/src/http_server/web_utils/get_authorization_header_api_key.rs b/crates/service/web/storyteller_web/src/http_server/web_utils/get_authorization_header_api_key.rs new file mode 100644 index 0000000000..fad5f03ba2 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/web_utils/get_authorization_header_api_key.rs @@ -0,0 +1,144 @@ +use actix_http::header::{HeaderMap, HeaderName}; +use actix_web::HttpRequest; + +use artcraft_api_keys::ArtcraftApiKey; + +const AUTHORIZATION_HEADER_NAME: HeaderName = HeaderName::from_static("authorization"); + +/// Extract an [`ArtcraftApiKey`] from a request's `Authorization` header. +/// +/// The header name is matched case-insensitively (HTTP header names always are). The value parsing +/// — the `Bearer `, `Key `, and bare `` forms — lives in the +/// [`artcraft_api_keys`] crate. Returns `None` if the header is absent, unreadable (non-ASCII +/// bytes), or does not contain a usable key. +pub fn get_authorization_header_api_key(http_request: &HttpRequest) -> Option { + let header_map: &HeaderMap = http_request.headers(); + let header_value = header_map.get(AUTHORIZATION_HEADER_NAME)? + .to_str() + .ok()?; + + ArtcraftApiKey::parse_from_authorization_header_value(header_value) +} + +#[cfg(test)] +mod tests { + use actix_web::test::TestRequest; + + use super::*; + + const SAMPLE_KEY: &str = "artcraft_api_55ax0zhd580m598r6n4n7szdwjb2b28sypapvawh"; + + mod request_header_tests { + use super::*; + + #[test] + fn bearer_scheme() { + assert_eq!( + api_key_for_authorization_header(&format!("Bearer {SAMPLE_KEY}")), + Some(SAMPLE_KEY.to_string())); + } + + #[test] + fn key_scheme() { + assert_eq!( + api_key_for_authorization_header(&format!("Key {SAMPLE_KEY}")), + Some(SAMPLE_KEY.to_string())); + } + + #[test] + fn bare_key_aws_style() { + assert_eq!( + api_key_for_authorization_header(SAMPLE_KEY), + Some(SAMPLE_KEY.to_string())); + } + + #[test] + fn header_name_is_case_insensitive() { + let value = format!("Bearer {SAMPLE_KEY}"); + for header_name in ["authorization", "Authorization", "AUTHORIZATION"] { + let http_request = TestRequest::default() + .insert_header((header_name, value.as_str())) + .to_http_request(); + assert_eq!( + get_authorization_header_api_key(&http_request).map(|key| key.to_string_be_careful()), + Some(SAMPLE_KEY.to_string()), + "failed for header name {header_name:?}"); + } + } + + #[test] + fn missing_header_returns_none() { + let http_request = TestRequest::default().to_http_request(); + assert_eq!(get_authorization_header_api_key(&http_request), None); + } + } + + mod parsing_tests { + use super::*; + + #[test] + fn bearer_scheme_is_case_insensitive() { + assert_eq!(parse_authorization_header_api_key("Bearer abc"), Some("abc".to_string())); + assert_eq!(parse_authorization_header_api_key("bearer abc"), Some("abc".to_string())); + assert_eq!(parse_authorization_header_api_key("BEARER abc"), Some("abc".to_string())); + assert_eq!(parse_authorization_header_api_key("BeArEr abc"), Some("abc".to_string())); + } + + #[test] + fn key_scheme_is_case_insensitive() { + assert_eq!(parse_authorization_header_api_key("Key abc"), Some("abc".to_string())); + assert_eq!(parse_authorization_header_api_key("key abc"), Some("abc".to_string())); + assert_eq!(parse_authorization_header_api_key("KEY abc"), Some("abc".to_string())); + } + + #[test] + fn bare_key_aws_style() { + assert_eq!(parse_authorization_header_api_key("abc"), Some("abc".to_string())); + assert_eq!(parse_authorization_header_api_key(SAMPLE_KEY), Some(SAMPLE_KEY.to_string())); + } + + #[test] + fn surrounding_and_inner_whitespace_is_trimmed() { + assert_eq!(parse_authorization_header_api_key(" Bearer abc "), Some("abc".to_string())); + assert_eq!(parse_authorization_header_api_key("\tKey\tabc\t"), Some("abc".to_string())); + assert_eq!(parse_authorization_header_api_key(" abc "), Some("abc".to_string())); + } + + #[test] + fn empty_or_blank_is_none() { + assert_eq!(parse_authorization_header_api_key(""), None); + assert_eq!(parse_authorization_header_api_key(" "), None); + } + + #[test] + fn scheme_with_no_credential_is_none() { + assert_eq!(parse_authorization_header_api_key("Bearer"), None); + assert_eq!(parse_authorization_header_api_key("Bearer "), None); + assert_eq!(parse_authorization_header_api_key("Key"), None); + assert_eq!(parse_authorization_header_api_key("key "), None); + } + + #[test] + fn unsupported_scheme_is_none() { + assert_eq!(parse_authorization_header_api_key("Basic dXNlcjpwYXNz"), None); + assert_eq!(parse_authorization_header_api_key("Digest abc"), None); + // Two non-scheme tokens — a bare key never contains whitespace. + assert_eq!(parse_authorization_header_api_key("abc def"), None); + } + } + + // Thin string-returning adapters over the real implementation so the original test cases above + // can stay unchanged after the parser moved into the `artcraft_api_keys` crate. + + fn parse_authorization_header_api_key(header_value: &str) -> Option { + ArtcraftApiKey::parse_from_authorization_header_value(header_value) + .map(|key| key.to_string_be_careful()) + } + + fn api_key_for_authorization_header(value: &str) -> Option { + let http_request = TestRequest::default() + .insert_header(("Authorization", value)) + .to_http_request(); + get_authorization_header_api_key(&http_request).map(|key| key.to_string_be_careful()) + } +} diff --git a/crates/service/web/storyteller_web/src/http_server/web_utils/mod.rs b/crates/service/web/storyteller_web/src/http_server/web_utils/mod.rs index a543348861..2617673672 100644 --- a/crates/service/web/storyteller_web/src/http_server/web_utils/mod.rs +++ b/crates/service/web/storyteller_web/src/http_server/web_utils/mod.rs @@ -1,5 +1,6 @@ pub mod bucket_urls; pub mod filter_model_name; +pub mod get_authorization_header_api_key; pub mod get_host_header; pub mod get_request_platform_type; pub mod handle_multipart_error; diff --git a/crates/service/web/storyteller_web/src/util/generate_api_key.rs b/crates/service/web/storyteller_web/src/util/generate_api_key.rs index e6e486a3e8..9ea2c8e10e 100644 --- a/crates/service/web/storyteller_web/src/util/generate_api_key.rs +++ b/crates/service/web/storyteller_web/src/util/generate_api_key.rs @@ -1,5 +1,7 @@ use rand::Rng; +use artcraft_api_keys::ArtcraftApiKey; + /// All Artcraft API keys begin with this prefix. It counts toward the total key length. /// /// The underscores in this literal are the ONLY underscores permitted in a key — the random @@ -27,7 +29,10 @@ const ENTROPY_CHAR_COUNT: usize = 40; /// /// Infallible: it draws from the thread-local CSPRNG (`rand::rng()`, a ChaCha-based, OS-seeded /// generator), which never fails. -pub fn generate_api_key() -> String { +/// +/// The full secret lives inside the returned [`ArtcraftApiKey`]; use `as_str()` to obtain it for +/// storage (`Debug`/`Display` deliberately redact it). +pub fn generate_api_key() -> ArtcraftApiKey { let mut rng = rand::rng(); let mut key = String::with_capacity(API_KEY_PREFIX.len() + ENTROPY_CHAR_COUNT); @@ -38,7 +43,7 @@ pub fn generate_api_key() -> String { key.push(CROCKFORD_LOWERCASE_CHARSET[index] as char); } - key + ArtcraftApiKey(key) } #[cfg(test)] @@ -49,19 +54,19 @@ mod tests { #[test] fn starts_with_prefix() { - assert!(generate_api_key().starts_with("artcraft_api_")); - assert!(generate_api_key().starts_with(API_KEY_PREFIX)); + assert!(generate_api_key().as_str_be_careful().starts_with("artcraft_api_")); + assert!(generate_api_key().as_str_be_careful().starts_with(API_KEY_PREFIX)); } #[test] fn has_expected_total_length() { - assert_eq!(generate_api_key().len(), API_KEY_PREFIX.len() + ENTROPY_CHAR_COUNT); + assert_eq!(generate_api_key().as_str_be_careful().len(), API_KEY_PREFIX.len() + ENTROPY_CHAR_COUNT); } #[test] fn entropy_suffix_is_crockford_lowercase() { let key = generate_api_key(); - let suffix = key.strip_prefix(API_KEY_PREFIX).expect("key must start with the prefix"); + let suffix = key.as_str_be_careful().strip_prefix(API_KEY_PREFIX).expect("key must start with the prefix"); assert_eq!(suffix.len(), ENTROPY_CHAR_COUNT); for byte in suffix.bytes() { @@ -79,21 +84,21 @@ mod tests { fn forbidden_characters_are_absent() { let key = generate_api_key(); for forbidden in ['.', '+', '/'] { - assert!(!key.contains(forbidden), "key must not contain {:?}", forbidden); + assert!(!key.as_str_be_careful().contains(forbidden), "key must not contain {:?}", forbidden); } } #[test] fn underscores_only_appear_in_the_prefix() { let key = generate_api_key(); - let suffix = key.strip_prefix(API_KEY_PREFIX).expect("key must start with the prefix"); + let suffix = key.as_str_be_careful().strip_prefix(API_KEY_PREFIX).expect("key must start with the prefix"); assert!(!suffix.contains('_'), "the random body must not contain underscores"); } #[test] fn entropy_suffix_is_alphanumeric() { let key = generate_api_key(); - let suffix = key.strip_prefix(API_KEY_PREFIX).expect("key must start with the prefix"); + let suffix = key.as_str_be_careful().strip_prefix(API_KEY_PREFIX).expect("key must start with the prefix"); assert!(suffix.chars().all(|c| c.is_ascii_alphanumeric())); } From d27d5cec75b453ebcd9329ca0a0a20762ec6e4f3 Mon Sep 17 00:00:00 2001 From: Tanish Manku Date: Mon, 15 Jun 2026 17:53:15 -0700 Subject: [PATCH 02/53] PageDraw: Port and mount to web app (#1650) --- _docs/pagedraw_design_review.md | 203 +++++++++++++ frontend/apps/artcraft-webapp/src/app/app.tsx | 2 + .../components/lightbox/LightboxDetails.tsx | 12 + .../src/components/lightbox/lightbox.tsx | 13 + .../src/components/sidebar/app-sidebar.tsx | 2 + .../pages/pagedraw/base-image-selector.tsx | 223 ++++++++++++++ .../src/pages/pagedraw/edit-on-canvas.ts | 27 ++ .../src/pages/pagedraw/index.ts | 1 + .../src/pages/pagedraw/job-polling.ts | 141 +++++++++ .../src/pages/pagedraw/pagedraw.tsx | 25 ++ .../src/pages/pagedraw/web-adapter.tsx | 275 ++++++++++++++++++ 11 files changed, 924 insertions(+) create mode 100644 _docs/pagedraw_design_review.md create mode 100644 frontend/apps/artcraft-webapp/src/pages/pagedraw/base-image-selector.tsx create mode 100644 frontend/apps/artcraft-webapp/src/pages/pagedraw/edit-on-canvas.ts create mode 100644 frontend/apps/artcraft-webapp/src/pages/pagedraw/index.ts create mode 100644 frontend/apps/artcraft-webapp/src/pages/pagedraw/job-polling.ts create mode 100644 frontend/apps/artcraft-webapp/src/pages/pagedraw/pagedraw.tsx create mode 100644 frontend/apps/artcraft-webapp/src/pages/pagedraw/web-adapter.tsx diff --git a/_docs/pagedraw_design_review.md b/_docs/pagedraw_design_review.md new file mode 100644 index 0000000000..cdcd0cb93b --- /dev/null +++ b/_docs/pagedraw_design_review.md @@ -0,0 +1,203 @@ +# PageDraw — Visual Design & Architecture Review + +**Surface:** `@storyteller/ui-pagedraw` image editor + its `artcraft-webapp` integration (`/edit-image`). +**Lens:** Awwwards / Linear-tier "premium app chrome" rubric, adapted for an in-product creative tool. +**Date:** 2026-06-15 · **Branch:** `worktree-pagedraw-webapp` + +--- + +## 0. How to read this + +PageDraw is a **professional canvas tool**, not a marketing page. So the elite-design rubric is +applied in its *app-chrome* register — the north star is **Figma / Linear / Photoshop-web**, not an +Awwwards landing page. That means: + +- **Applies:** token discipline, concentric radii, hairline + inset-highlight surfaces ("double-bezel"), + island/pill controls, custom-eased micro-motion, z-index hygiene, premium type ramp. +- **Does _not_ apply:** `py-40` macro-whitespace, hero typography, editorial-split layouts, scroll-reveal + entrances. A tool surface is dense and persistent by design; forcing landing-page whitespace would hurt it. + +Citations are `file:line` against the current tree. + +--- + +## 1. Verdict + +| Axis | Score | One-liner | +|---|---|---| +| Color system | ⚠️ 5/10 | Good token foundation, but hardcoded greys (`#1b1b1b`, `#303030`, `#404040`) leak through a *themeable lib*. | +| Typography | ❌ 3/10 | No type ramp, no font-family ownership; rides whatever the host sets. Banned-by-rubric default stack risk. | +| Radii | ⚠️ 4/10 | Five radius scales in play (`sm`→`2xl`→`full`) with no concentric nesting. | +| Surfaces (border/shadow) | ⚠️ 5/10 | Flat `shadow-lg` everywhere; no inset highlights; no double-bezel. Reads "functional", not "machined". | +| Motion | ❌ 3/10 | Only `transition-colors`/`ease-in-out`, durations 75–200ms; **zero custom easing**, no spring, no entrance choreography. | +| Layout / z-index | ⚠️ 6/10 | Clean fixed-panel composition, but a `z-[9999999]` escape hatch and a 6-tier ad-hoc z-stack. | +| Icons | ✅ 7/10 | FontAwesome Pro (solid + regular) — acceptable, but solid weight is heavier than the rubric's "ultra-light line" ideal. | +| Architecture | ⚠️ 5/10 | Clean adapter boundary; but `PaintSurface.tsx` (2005 LOC), `SceneState.ts` (1539), `PageDraw.tsx` (1292) are oversized, and the lib hardcodes host-theme colors. | + +**Bottom line:** the editor is *structurally competent and functional* but visually reads as +"capable internal tool," not "$150k product surface." The single highest-leverage fix is a **shared +design-token layer** (color + radius + motion + type), because today the lib both (a) reaches for +host tokens (`bg-ui-panel`, `text-base-fg`) *and* (b) hardcodes its own greys — so it can never be +fully reskinned by the webapp vs. the desktop app. + +--- + +## 2. What's already good (keep) + +- **Token-aware base.** Most surfaces use semantic tokens (`bg-ui-panel`, `bg-ui-controls`, + `text-base-fg`, `border-ui-panel-border`) — the right instinct (SideToolbar, PromptEditor, HistoryStack). +- **Clean platform boundary.** The lib has zero REST/Tauri imports; everything I/O flows through + `PageDrawAdapter` (`adapter.ts`). The webapp adapter (`web-adapter.tsx`) and desktop adapter stay + swappable. This is genuinely good architecture and should be protected. +- **Performance-minded canvas.** Composite pre-baking on idle (`PageDraw.tsx` ~661–710) and worker + encoding (`generatePipeline.worker.ts`) keep the generate click off the main thread. Don't regress this. +- **CORS already handled** for browser canvas export (`crossOrigin="anonymous"` in `Node.ts:134`, + `SceneState.ts:1473`, `imageHelpers.ts:16`) — the web port works because of this. + +--- + +## 3. Findings & recommendations + +### 3.1 Color — stop hardcoding greys in a themeable lib ⚠️ P0 +**Current:** semantic tokens *and* raw hex coexist: +- `#1b1b1b` pegboard (`pagedraw.css:6`), `#1A1A1A`/`#333333` context menu (`ContextMenu.tsx:44`), + `#303030`/`#404040` dividers + popovers (`SideToolbar.tsx:381,550`), `bg-zinc-700/600` swatch + (`SideToolbar.tsx:173`), plus ad-hoc `bg-blue-600/500` accents (`PageDraw.tsx:157`, base selector). +- Accent color is inconsistent: the lib's primary action uses raw **blue-600**, while the webapp's + design system uses `--primary` / `brand-primary`. So the editor's blue doesn't match the product. + +**Why it fails the bar:** a premium component library exposes *one* surface ramp and *one* accent, both +driven by tokens, so it reskins per host. Hardcoded greys mean the desktop and web themes can drift, and +the raw `blue-600` clashes with `brand-primary` used elsewhere in the webapp. + +**Do:** +- Replace every raw grey with the existing ramp: `#1b1b1b`→`bg-ui-background`, `#1A1A1A`→`bg-ui-panel`, + `#303030`/`#404040`→`border-ui-panel-border` / `bg-ui-controls`. For the pegboard, add a token + `--st-canvas` so hosts can theme the canvas backdrop. +- Replace `blue-600/500` accents with `bg-primary` / `text-primary` (the token the rest of the webapp uses), + so the Generate/Edit-3D buttons match `brand-primary`. +- Move the react-colorful overrides off pure `rgb(255,255,255)` borders (`pagedraw.css:15,19`) onto + `white/15` hairlines for consistency with the rest of the chrome. + +### 3.2 Typography — own a type ramp ❌ P0 +**Current:** no `font-family` anywhere in the lib (grep: 0 hits); only `text-xs/sm/base/lg/xl` + +`font-medium/semibold/bold`. The editor inherits whatever the host's body font is. + +**Why it fails the bar:** the rubric explicitly bans the default web stack (Inter/Roboto/Arial/Helvetica). +Inheriting an unspecified stack means the tool can render in a banned font with no recourse, and there's +no deliberate hierarchy (numerics in sliders, labels, modal titles all share one undifferentiated ramp). + +**Do:** +- Adopt the product's display/grotesk token (e.g. a `font-sans` mapped to Geist / Plus Jakarta Sans at + the app level) and reference it in the lib's root container, not per-component. +- Define a small semantic ramp: `text-[11px] tracking-[0.16em] uppercase` for toolbar/section labels, + `text-sm` for controls, `text-base font-semibold` for modal titles. Apply `tabular-nums` to slider + read-outs (`SliderWithIndicator.tsx`) and dimension fields (`BlankCanvasModal`) so numbers don't jitter. + +### 3.3 Radii — collapse to a concentric scale ⚠️ P1 +**Current:** `rounded-sm / md / lg / xl / 2xl / full` all appear, with no parent→child relationship +(e.g. `rounded-2xl` card at `PageDraw.tsx:1123`, `rounded-xl` panels in SideToolbar, `rounded-lg` +buttons in InpaintToolBar). + +**Do:** pick a 3-step system — **`rounded-2xl` shells, `rounded-xl` inner cards, `rounded-lg` controls, +`rounded-full` pills** — and apply concentrically (the "double-bezel" curve math: inner radius = +outer − padding). Drop `rounded-sm`/`rounded-md` from the editor entirely. + +### 3.4 Surfaces — add the "machined" double-bezel ⚠️ P1 +**Current:** panels are a single `bg-ui-panel` + `border` + `shadow-lg`. No nested enclosure, no inset +top-highlight. Reads flat. + +**Do (for the four floating panels — SideToolbar, HistoryStack, PromptEditor, InpaintToolBar):** +- Outer shell: `bg-ui-panel/80 ring-1 ring-white/10 backdrop-blur-xl p-1.5 rounded-2xl`. +- Inner core: own bg + `shadow-[inset_0_1px_0_rgba(255,255,255,0.08)] rounded-xl`. +- This is the single change that most moves the surface from "tool" to "premium hardware." The panels are + already `fixed` (`PageDraw.tsx:1144,1175,1195`), so `backdrop-blur` here is GPU-safe per the perf rules. + +### 3.5 Motion — replace linear/ease with spring-eased micro-motion ❌ P0 (cheap, high impact) +**Current:** `transition-colors`/`transition-all` with `ease-in-out` and `duration-75/150/200` +(`SideToolbar.tsx:381,550`, `SliderWithIndicator.tsx:65`, `ContextMenu.tsx:53`). No custom easing, +no entrance/exit on popovers, color pickers, or the context menu (they pop instantly). + +**Why it fails the bar:** instant state changes and `ease-in-out` are explicitly banned. A pro tool still +deserves *interpolated* motion — it's what separates Figma from a jQuery plugin. + +**Do:** +- Standardize on one curve: `transition-[transform,opacity,background-color] duration-300 + ease-[cubic-bezier(0.32,0.72,0,1)]`. +- Tool buttons: `active:scale-[0.97]` press physics; selected tool gets a spring-eased indicator slide + rather than a hard background swap. +- Popovers / color picker / context menu: animate in with `opacity-0 translate-y-1 scale-[0.98]` → + resolved, ~180ms. Transform+opacity only (perf rule). +- History tiles: stagger their `opacity` reveal when a generation lands (they already use + `transition-opacity`, just add per-index delay). + +### 3.6 Layout & z-index — tame the stack ⚠️ P1 +**Current:** clean fixed-panel composition, but z-index is ad-hoc across 6 tiers and includes a +`z-[9999999]` on the context menu (`ContextMenu.tsx:44`) and a one-off `z-[60]` (`PageDraw.tsx:237`). + +**Do:** define a named scale in one place — `canvas:0, chrome:10, floating-controls:20, overlay-button:40, +menu:50` — and replace the magic numbers. The portal menu only needs to clear the chrome, not `9999999`. + +### 3.7 Responsive — declare intent ⚠️ P2 +**Current:** essentially desktop-only; fixed sidebars assume a wide viewport; only LightboxDetails has +`sm:` variants. The webapp wrapper scopes the lib correctly (`pagedraw.tsx` `translateZ(0)` containing +block) but there's no mobile story. + +**Do:** mirror the Edit-3D page's explicit **mobile gate** (`pagescene.tsx` `MobileGate`) on `/edit-image` +rather than letting fixed toolbars overlap on touch. A precision canvas tool is legitimately desktop-first; +just say so deliberately instead of degrading silently. + +--- + +## 4. Architecture notes + +- **Oversized modules.** `PaintSurface.tsx` (2005 LOC), `SceneState.ts` (1539), `PageDraw.tsx` (1292). + These are review/maintenance hotspots. Suggest extracting from `SceneState.ts`: history/undo slice, + base-image-load slice, and generation/pending slice into composable Zustand slices. `PageDraw.tsx`'s + generate-flow (cases 1–3, ~780–868) could move to a `useGenerate()` hook. +- **Theme ownership.** Per §3.1, the lib should *only* consume tokens. Today it mixes tokens + hex, which + defeats the adapter pattern's promise (one component, many themed hosts). +- **Adapter surface is healthy.** `PageDrawAdapter` is small and intentional; the web vs. Tauri split is + clean. The webapp's `enqueueInpaint` stub and 402→credits-modal handling are explicit and documented. +- **Icon weight.** FontAwesome **solid** is the heaviest option; if a line/light set is available + (Phosphor/Remix or FA light), the toolbar would read more refined. Low priority. + +--- + +## 5. Prioritized roadmap + +| P | Item | Effort | Payoff | +|---|---|---|---| +| **P0** | Token sweep: kill hardcoded greys; unify accent on `--primary` (§3.1) | M | Highest — enables true theming + product-consistent accent | +| **P0** | Own a font-family + semantic type ramp at the lib root (§3.2) | S | High — removes banned-font risk, adds hierarchy | +| **P0** | One custom easing curve + press/popover micro-motion (§3.5) | S | High — cheapest "feels expensive" win | +| **P1** | Double-bezel + inset highlights on the 4 floating panels (§3.4) | M | High — "machined" surface upgrade | +| **P1** | Concentric 3-step radius system (§3.3) | S | Medium — visual coherence | +| **P1** | Named z-index scale; remove `z-[9999999]` (§3.6) | S | Medium — maintainability | +| **P2** | Explicit mobile gate on `/edit-image` (§3.7) | S | Medium — correctness on touch | +| **P2** | Split `SceneState.ts` / `PaintSurface.tsx` into slices (§4) | L | Medium — maintainability | + +--- + +## 6. Appendix — proposed token additions + +```css +/* canvas + chrome surface ramp (lib should consume only these) */ +--st-canvas: ; /* replaces #1b1b1b pegboard */ +--st-accent: var(--primary); /* replaces blue-600/500 */ +--st-hairline: rgb(255 255 255 / 0.10); +--st-inset-hi: inset 0 1px 0 rgb(255 255 255 / 0.08); + +/* motion */ +--st-ease: cubic-bezier(0.32, 0.72, 0, 1); +--st-dur-fast: 180ms; +--st-dur: 300ms; + +/* radius scale (concentric) */ +--st-r-shell: 1.25rem; /* rounded-2xl */ +--st-r-card: 0.875rem; /* rounded-xl */ +--st-r-control: 0.5rem; /* rounded-lg */ +``` + +> Scope note: every recommendation here is **styling/architecture only** — none change the generate, +> polling, upload, bg-removal, or adapter contracts validated during the functional debug pass. diff --git a/frontend/apps/artcraft-webapp/src/app/app.tsx b/frontend/apps/artcraft-webapp/src/app/app.tsx index 2aae08da3e..4f0003483e 100644 --- a/frontend/apps/artcraft-webapp/src/app/app.tsx +++ b/frontend/apps/artcraft-webapp/src/app/app.tsx @@ -16,6 +16,7 @@ import CreateImage from "../pages/create-image"; import CreateVideo from "../pages/create-video"; import CreateVFX from "../pages/create-vfx"; import PageScene from "../pages/pagescene"; +import PageDraw from "../pages/pagedraw"; import VideoEditorPage from "../pages/video-editor"; import Pricing from "../pages/pricing"; import Support from "../pages/support/support"; @@ -149,6 +150,7 @@ export function App() { } /> } /> } /> + } /> } /> void; onRecreate?: () => void; onMakeVideo?: () => void; + onEditOnCanvas?: () => void; onDelete?: () => void; showDownloadAppCta?: boolean; } @@ -90,6 +91,7 @@ export function LightboxDetails({ onClose, onRecreate, onMakeVideo, + onEditOnCanvas, onDelete, showDownloadAppCta, }: LightboxDetailsProps) { @@ -474,6 +476,16 @@ export function LightboxDetails({ Make Video )} + {onEditOnCanvas && ( + + )} {onRecreate && promptData.hasToken && ( + + + + + { + setIsGalleryOpen(false); + setGallerySelectedIds([]); + }} + selectedItemIds={gallerySelectedIds} + onSelectItem={handleGallerySelect} + maxSelections={1} + onUseSelected={handleUseGalleryImages} + forceFilter="image" + /> + + setIsBlankCanvasOpen(false)} + onConfirm={handleBlankCanvasConfirm} + /> + + ); +} diff --git a/frontend/apps/artcraft-webapp/src/pages/pagedraw/edit-on-canvas.ts b/frontend/apps/artcraft-webapp/src/pages/pagedraw/edit-on-canvas.ts new file mode 100644 index 0000000000..efd68ed4dd --- /dev/null +++ b/frontend/apps/artcraft-webapp/src/pages/pagedraw/edit-on-canvas.ts @@ -0,0 +1,27 @@ +// "Edit on Canvas" handoff: open an existing image in the PageDraw editor. +// +// Mirrors the Tauri app's handleEditFromGallery — RESET the editor, seed the +// chosen image as the base (and into history), then route to /edit-image. +// Seeding the lib's store before navigating means the editor mounts straight +// into the image, same store-based handoff the create-image/create-video +// "Recreate" and "Make Video" buttons use (see lib/recreate.ts). + +import type { NavigateFunction } from "react-router-dom"; +import { useSceneStore } from "@storyteller/ui-pagedraw"; + +export function applyEditOnCanvasFromImage( + mediaToken: string, + mediaUrl: string, + navigate: NavigateFunction, +): void { + const store = useSceneStore.getState(); + const baseImage = { + url: mediaUrl, + mediaToken, + fullImageUrl: mediaUrl, + }; + store.RESET(); + store.addHistoryImageBundle({ images: [baseImage] }); + store.setBaseImageInfo(baseImage); + navigate("/edit-image"); +} diff --git a/frontend/apps/artcraft-webapp/src/pages/pagedraw/index.ts b/frontend/apps/artcraft-webapp/src/pages/pagedraw/index.ts new file mode 100644 index 0000000000..6de25f28d1 --- /dev/null +++ b/frontend/apps/artcraft-webapp/src/pages/pagedraw/index.ts @@ -0,0 +1 @@ +export { default } from "./pagedraw"; diff --git a/frontend/apps/artcraft-webapp/src/pages/pagedraw/job-polling.ts b/frontend/apps/artcraft-webapp/src/pages/pagedraw/job-polling.ts new file mode 100644 index 0000000000..7cb6567230 --- /dev/null +++ b/frontend/apps/artcraft-webapp/src/pages/pagedraw/job-polling.ts @@ -0,0 +1,141 @@ +// Job polling for the PageDraw web adapter. +// +// Mirrors the create-image / create-vfx job loops: enqueue returns an +// inference job token, then we poll `GET /v1/jobs/job/` until the +// job reaches a terminal state. On success we resolve the result media — +// preferring the batch (so multi-image generations all land) and falling +// back to the single media_links payload. +// +// Kept local to the pagedraw page (rather than imported from create-image) +// so the editor owns its own result shape and isn't coupled to that page's +// store types — same separation the artcraft-website pagedraw port used. + +import { JobsApi, MediaFilesApi } from "@storyteller/api"; + +export interface PolledImage { + media_token: string; + cdn_url: string; + maybe_thumbnail_template?: string; +} + +export type PollResult = + | { status: "pending"; images: PolledImage[] } + | { status: "complete"; images: PolledImage[] } + | { status: "failed"; images: PolledImage[]; error: string }; + +export async function pollJobOnce(jobToken: string): Promise { + const jobsApi = new JobsApi(); + const response = await jobsApi.GetJobByToken({ token: jobToken }); + + if (!response.success || !response.data) { + return { status: "pending", images: [] }; + } + + const state = response.data; + const statusStr = state.status?.status?.toLowerCase() ?? ""; + + if (statusStr === "complete_success" || statusStr === "complete") { + const result = state.maybe_result as Record | undefined; + const mediaLinks = (result as any)?.media_links; + const entityToken = (result as any)?.entity_token as string | undefined; + + if (entityToken) { + try { + const mediaApi = new MediaFilesApi(); + const mediaFile = await mediaApi.GetMediaFileByToken({ + mediaFileToken: entityToken, + }); + const batchToken = mediaFile.data?.maybe_batch_token; + if (batchToken) { + const batchResponse = await mediaApi.GetMediaFilesByBatchToken({ + batchToken, + }); + if (batchResponse.success && batchResponse.data?.length) { + const images: PolledImage[] = batchResponse.data + .map((file: any) => ({ + media_token: file.token, + cdn_url: file.media_links?.cdn_url, + maybe_thumbnail_template: + file.media_links?.maybe_thumbnail_template, + })) + .filter((img: PolledImage) => img.cdn_url); + if (images.length > 0) return { status: "complete", images }; + } + } + } catch { + // Fall through to single-image extraction. + } + } + + if (mediaLinks?.cdn_url) { + return { + status: "complete", + images: [ + { + media_token: entityToken ?? jobToken, + cdn_url: mediaLinks.cdn_url, + maybe_thumbnail_template: mediaLinks.maybe_thumbnail_template, + }, + ], + }; + } + + return { status: "complete", images: [] }; + } + + if ( + statusStr.includes("fail") || + statusStr.includes("error") || + statusStr === "dead" + ) { + return { + status: "failed", + images: [], + error: + state.status?.maybe_failure_message ?? + state.status?.maybe_extra_status_description ?? + "Generation failed", + }; + } + + return { status: "pending", images: [] }; +} + +export function startPolling( + jobToken: string, + onComplete: (images: PolledImage[]) => void, + onError: (reason: string) => void, + intervalMs = 3000, + maxAttempts = 120, +): () => void { + let attempts = 0; + let stopped = false; + + const poll = async () => { + if (stopped) return; + attempts++; + + try { + const result = await pollJobOnce(jobToken); + if (stopped) return; + + if (result.status === "complete") return onComplete(result.images); + if (result.status === "failed") return onError(result.error); + if (attempts >= maxAttempts) return onError("Generation timed out"); + + setTimeout(poll, intervalMs); + } catch { + if (!stopped && attempts < maxAttempts) { + setTimeout(poll, intervalMs * 2); + } else if (!stopped) { + onError("Network error during polling"); + } + } + }; + + setTimeout(poll, 2000); + + return () => { + stopped = true; + }; +} diff --git a/frontend/apps/artcraft-webapp/src/pages/pagedraw/pagedraw.tsx b/frontend/apps/artcraft-webapp/src/pages/pagedraw/pagedraw.tsx new file mode 100644 index 0000000000..eb9c17f92e --- /dev/null +++ b/frontend/apps/artcraft-webapp/src/pages/pagedraw/pagedraw.tsx @@ -0,0 +1,25 @@ +import { PageDraw as PageDrawLib } from "@storyteller/ui-pagedraw"; +import Seo from "../../components/seo"; +import { useWebPageDrawAdapter } from "./web-adapter"; + +export default function PageDraw() { + const adapter = useWebPageDrawAdapter(); + return ( + <> + + {/* PageDraw lays its canvas and toolbars out with position:fixed. + translateZ(0) makes this div their containing block so they scope + to the SidebarInset area instead of covering the whole window — + same technique the Edit 3D page uses for its lib overlays. */} +
+ +
+ + ); +} diff --git a/frontend/apps/artcraft-webapp/src/pages/pagedraw/web-adapter.tsx b/frontend/apps/artcraft-webapp/src/pages/pagedraw/web-adapter.tsx new file mode 100644 index 0000000000..ae316ce501 --- /dev/null +++ b/frontend/apps/artcraft-webapp/src/pages/pagedraw/web-adapter.tsx @@ -0,0 +1,275 @@ +// PageDrawAdapter implementation for the artcraft webapp. +// +// Plugs the shared @storyteller/ui-pagedraw lib into the webapp by speaking +// REST instead of Tauri commands: +// - enqueueEditImage → OmniGenApi.generateImage, then poll the job and feed +// results back into the lib's Zustand store (addHistoryImageBundle / +// resolvePendingGeneration), keyed on frontendSubscriberId. +// - enqueueBgRemoval → upload base64 → media token → POST +// /v1/generate/image/remove_background → poll → finishRemoveBackground. +// - uploadImage → UploadImageMedia (shared REST uploader). +// - renderBaseImageSelector → the webapp's own upload/library/blank picker. +// +// Inpaint is intentionally stubbed: the omni-gen REST endpoint doesn't yet +// accept a raw mask payload, so we surface a clear toast until that path +// exists. Mirrors the artcraft-website pagedraw port. + +import { useMemo } from "react"; +import { + type PageDrawAdapter, + type ImageBundle, + type BaseSelectorImage, + useSceneStore, +} from "@storyteller/ui-pagedraw"; +import { + OmniGenApi, + StorytellerApiHostStore, + UploadImageMedia, + type OmniGenImageRequest, +} from "@storyteller/api"; +import { UploaderStates } from "@storyteller/common"; +import { showToast } from "../../components/toast/toast"; +import { useInsufficientCredits } from "../../components/insufficient-credits-modal"; +import { BaseImageSelector } from "./base-image-selector"; +import { startPolling, type PolledImage } from "./job-polling"; + +// ─── Local bg-removal call ────────────────────────────────────────────────── +// Inlined (no dedicated method in @storyteller/api yet). Mirrors ApiManager's +// auth: cookies via credentials:"include", plus the signed-session header +// fallback for browsers that block third-party cookies. + +const SESSION_STORAGE_KEY = "artcraft_signed_session"; + +interface RemoveBgRequest { + uuid_idempotency_token: string; + media_file_token: string; +} + +interface RemoveBgResponse { + success: boolean; + inference_job_token: string; +} + +const sessionHeaders = (): Record => { + const headers: Record = { + Accept: "application/json", + "Content-Type": "application/json", + }; + try { + const session = localStorage.getItem(SESSION_STORAGE_KEY); + if (session) headers.session = session; + } catch { + // localStorage unavailable — rely on the cookie. + } + return headers; +}; + +const removeImageBackground = async ( + body: RemoveBgRequest, +): Promise => { + const host = StorytellerApiHostStore.getInstance().getApiSchemeAndHost(); + const response = await fetch(`${host}/v1/generate/image/remove_background`, { + method: "POST", + headers: sessionHeaders(), + credentials: "include", + body: JSON.stringify(body), + }); + if (!response.ok) { + throw new Error(`Background removal HTTP ${response.status}`); + } + return response.json(); +}; + +// ─── Helpers ───────────────────────────────────────────────────────────────── + +const polledImagesToBundle = (images: PolledImage[]): ImageBundle => ({ + images: images.map( + (img) => + ({ + url: img.cdn_url, + mediaToken: img.media_token, + thumbnailUrlTemplate: img.maybe_thumbnail_template, + fullImageUrl: img.cdn_url, + }) as BaseSelectorImage, + ), +}); + +const startGenerationPoll = (jobToken: string, subscriberId: string) => { + startPolling( + jobToken, + (images) => { + const store = useSceneStore.getState(); + // The user may have dismissed the placeholder while the job ran. + if (!store.pendingGenerations.some((p) => p.id === subscriberId)) return; + store.addHistoryImageBundle(polledImagesToBundle(images)); + store.resolvePendingGeneration(subscriberId); + }, + (reason) => { + showToast("error", reason); + useSceneStore.getState().resolvePendingGeneration(subscriberId); + }, + ); +}; + +const base64ToBlob = (base64: string): Blob => { + const commaIdx = base64.indexOf(","); + const raw = commaIdx >= 0 ? base64.slice(commaIdx + 1) : base64; + const headerMatch = + commaIdx >= 0 ? base64.slice(0, commaIdx).match(/data:(.*?);base64/) : null; + const mime = headerMatch?.[1] ?? "image/png"; + const binary = atob(raw); + const bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i); + return new Blob([bytes], { type: mime }); +}; + +const uploadBlobAsImage = async ( + blob: Blob, + title: string, +): Promise => { + let mediaToken: string | undefined; + const file = new File([blob], `${title}.png`, { type: blob.type }); + await UploadImageMedia({ + title, + assetFile: file, + progressCallback: (state) => { + if (state.status === UploaderStates.success && state.data) { + mediaToken = state.data; + } + }, + }); + return mediaToken; +}; + +// PageDrawEditRequest.model is an ImageModel object (or possibly a raw id); +// the omni-gen REST body wants the string id. +const modelIdOf = (model: unknown): string => { + if (!model) return ""; + if (typeof model === "string") return model; + const m = model as { id?: string; tauriId?: string }; + return m.id ?? m.tauriId ?? String(model); +}; + +// ApiManager throws Error("HTTP error! status: 402") on a non-2xx response, and +// our inlined bg-removal fetch throws "Background removal HTTP 402"; the JSON +// body isn't surfaced either way. Pull the 3-digit status out of the message so +// callers can special-case 402 Payment Required. +const httpStatusFromError = (e: unknown): number | undefined => { + const message = e instanceof Error ? e.message : String(e); + const match = /(\d{3})/.exec(message); + return match ? Number(match[1]) : undefined; +}; + +// ─── Adapter ─────────────────────────────────────────────────────────────────── + +export const useWebPageDrawAdapter = (): PageDrawAdapter => { + const openInsufficientCredits = useInsufficientCredits(); + + return useMemo( + () => ({ + enqueueEditImage: async (req) => { + const refTokens = [ + ...(req.canvasImageMediaToken ? [req.canvasImageMediaToken] : []), + ...(req.imageMediaTokens ?? []), + ].filter((t): t is string => !!t); + + const body: OmniGenImageRequest = { + model: modelIdOf(req.model), + prompt: req.prompt ?? null, + idempotency_token: crypto.randomUUID(), + aspect_ratio: req.aspectRatio ?? null, + resolution: req.imageResolution ?? null, + image_batch_count: req.imageCount ?? 1, + image_media_tokens: refTokens.length ? refTokens : null, + }; + + try { + const response = await new OmniGenApi().generateImage(body); + if ( + response.success && + response.inference_job_token && + req.frontendSubscriberId + ) { + startGenerationPoll( + response.inference_job_token, + req.frontendSubscriberId, + ); + return { status: "success" }; + } + showToast("error", "Generation failed to enqueue"); + return { status: "fail" }; + } catch (e: any) { + // 402 Payment Required → the user is out of credits. Open the + // upgrade/credits modal instead of a raw error toast, matching the + // create-image / create-video generate handlers. + if (httpStatusFromError(e) === 402) { + openInsufficientCredits(); + return { status: "fail" }; + } + showToast("error", `Generation error: ${e?.message ?? e}`); + return { status: "fail" }; + } + }, + + enqueueInpaint: async (req) => { + // The omni-gen REST endpoint doesn't yet accept a raw mask payload; + // surface clearly until a multipart/mask-upload path exists. + if (!req.maskImageRawBytes) { + showToast("error", "Inpaint mask missing"); + return { status: "fail" }; + } + showToast("error", "Inpainting is not yet available on the web"); + return { status: "fail" }; + }, + + enqueueBgRemoval: async (base64Image, nodeId) => { + try { + const blob = base64ToBlob(base64Image); + const mediaToken = await uploadBlobAsImage( + blob, + `bg-removal-${nodeId}`, + ); + if (!mediaToken) { + showToast("error", "Background removal upload failed"); + return; + } + const response = await removeImageBackground({ + uuid_idempotency_token: crypto.randomUUID(), + media_file_token: mediaToken, + }); + if (!response.success || !response.inference_job_token) { + showToast("error", "Background removal failed to enqueue"); + return; + } + startPolling( + response.inference_job_token, + async (images) => { + const out = images[0]; + if (!out) return; + await useSceneStore + .getState() + .finishRemoveBackground(nodeId, out.media_token, out.cdn_url); + }, + (reason) => showToast("error", reason), + ); + } catch (e: any) { + if (httpStatusFromError(e) === 402) { + openInsufficientCredits(); + return; + } + showToast("error", `Background removal error: ${e?.message ?? e}`); + } + }, + + uploadImage: UploadImageMedia, + + renderBaseImageSelector: ({ onImageSelect, showLoading }) => ( + + ), + }), + [openInsufficientCredits], + ); +}; From e30b4366ce584d00000e750f68628f5e05ff9e5f Mon Sep 17 00:00:00 2001 From: BFlat Date: Tue, 16 Jun 2026 09:06:38 +0700 Subject: [PATCH 03/53] Update SettingsModal.tsx to flip api feature flag --- .../src/components/settings-modal/SettingsModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/apps/artcraft-webapp/src/components/settings-modal/SettingsModal.tsx b/frontend/apps/artcraft-webapp/src/components/settings-modal/SettingsModal.tsx index 3abfd45603..18bbe29dee 100644 --- a/frontend/apps/artcraft-webapp/src/components/settings-modal/SettingsModal.tsx +++ b/frontend/apps/artcraft-webapp/src/components/settings-modal/SettingsModal.tsx @@ -37,7 +37,7 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) { ); const tabs = useMemo( - () => (!hasApiKeyFlag ? [...BASE_TABS, API_KEYS_TAB] : BASE_TABS), + () => (hasApiKeyFlag ? [...BASE_TABS, API_KEYS_TAB] : BASE_TABS), [hasApiKeyFlag], ); From 1e20f2bfffee25af2bc167d36985ad801e5f8fcd Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Mon, 15 Jun 2026 22:08:31 -0400 Subject: [PATCH 04/53] omni api image and video (#1652) --- ...503bbbef045fd193a5a37a66180fcac77f363.json | 47 ++ .../api_keys/get_api_key_user_by_value.rs | 63 +++ .../mysql_queries/src/queries/api_keys/mod.rs | 1 + .../web/storyteller_web/src/docs/api_doc.rs | 3 + .../src/http_server/endpoints/mod.rs | 1 + .../image/hydrate_to_router_request.rs | 100 ++++ .../image/insert_db_job/insert_fal_job.rs | 61 +++ .../insert_db_job/insert_seedance2pro_jobs.rs | 117 +++++ .../generate/image/insert_db_job/mod.rs | 3 + .../image/insert_db_job/shared_job_args.rs | 22 + .../endpoints/omni_api/generate/image/mod.rs | 5 + .../image/omni_api_image_generate_handler.rs | 313 ++++++++++++ .../generate/image/pipeline_result.rs | 15 + .../generate/image/pipeline_v2/mod.rs | 1 + .../image/pipeline_v2/run_pipeline_v2.rs | 208 ++++++++ .../endpoints/omni_api/generate/mod.rs | 2 + .../generate/video/helpers/bill_wallet.rs | 44 ++ .../video/helpers/build_router_client.rs | 61 +++ .../video/helpers/hydrate_router_request.rs | 97 ++++ .../omni_api/generate/video/helpers/mod.rs | 6 + .../generate/video/helpers/pipeline_result.rs | 14 + .../helpers/resolve_kinovi_character_ids.rs | 51 ++ .../helpers/resolve_media_tokens_to_urls.rs | 64 +++ .../video/insert_db_job/insert_fal_job.rs | 57 +++ .../insert_db_job/insert_gmicloud_job.rs | 48 ++ .../insert_db_job/insert_grok_api_job.rs | 48 ++ .../insert_db_job/insert_seedance2pro_jobs.rs | 95 ++++ .../generate/video/insert_db_job/mod.rs | 5 + .../video/insert_db_job/shared_job_args.rs | 22 + .../omni_api/generate/video/kinovi_account.rs | 7 + .../endpoints/omni_api/generate/video/mod.rs | 5 + .../video/omni_api_video_generate_handler.rs | 477 ++++++++++++++++++ .../generate/video/pipeline_v2/mod.rs | 1 + .../video/pipeline_v2/run_pipeline_v2.rs | 227 +++++++++ .../src/http_server/endpoints/omni_api/mod.rs | 2 + .../map_seedance2pro_router_error.rs | 90 ++++ .../endpoints/omni_api/shared_utils/mod.rs | 2 + .../omni_api/shared_utils/video/mod.rs | 2 + .../video/validate_video_request.rs | 11 + .../video/validate_when_image_required.rs | 157 ++++++ .../storyteller_web/src/http_server/mod.rs | 1 + .../add_application_routes.rs | 2 + .../routes/application_routes/mod.rs | 1 + .../application_routes/omni_api_routes.rs | 36 ++ .../http_server/user_lookup/api_keys/mod.rs | 1 + .../api_keys/require_api_key_user.rs | 83 +++ .../src/http_server/user_lookup/mod.rs | 1 + 47 files changed, 2680 insertions(+) create mode 100644 .sqlx/query-f52c495a7ff5cafb357f79c4cdb503bbbef045fd193a5a37a66180fcac77f363.json create mode 100644 crates/schema/database/mysql_queries/src/queries/api_keys/get_api_key_user_by_value.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/hydrate_to_router_request.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/insert_db_job/insert_fal_job.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/insert_db_job/insert_seedance2pro_jobs.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/insert_db_job/mod.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/insert_db_job/shared_job_args.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/mod.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/omni_api_image_generate_handler.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/pipeline_result.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/pipeline_v2/mod.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/pipeline_v2/run_pipeline_v2.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/mod.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/helpers/bill_wallet.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/helpers/build_router_client.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/helpers/hydrate_router_request.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/helpers/mod.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/helpers/pipeline_result.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/helpers/resolve_kinovi_character_ids.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/helpers/resolve_media_tokens_to_urls.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/insert_db_job/insert_fal_job.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/insert_db_job/insert_gmicloud_job.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/insert_db_job/insert_grok_api_job.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/insert_db_job/insert_seedance2pro_jobs.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/insert_db_job/mod.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/insert_db_job/shared_job_args.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/kinovi_account.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/mod.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/omni_api_video_generate_handler.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/pipeline_v2/mod.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/pipeline_v2/run_pipeline_v2.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/mod.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/shared_utils/map_seedance2pro_router_error.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/shared_utils/mod.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/shared_utils/video/mod.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/shared_utils/video/validate_video_request.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/shared_utils/video/validate_when_image_required.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/routes/application_routes/omni_api_routes.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/user_lookup/api_keys/mod.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/user_lookup/api_keys/require_api_key_user.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/user_lookup/mod.rs diff --git a/.sqlx/query-f52c495a7ff5cafb357f79c4cdb503bbbef045fd193a5a37a66180fcac77f363.json b/.sqlx/query-f52c495a7ff5cafb357f79c4cdb503bbbef045fd193a5a37a66180fcac77f363.json new file mode 100644 index 0000000000..780eabe4e3 --- /dev/null +++ b/.sqlx/query-f52c495a7ff5cafb357f79c4cdb503bbbef045fd193a5a37a66180fcac77f363.json @@ -0,0 +1,47 @@ +{ + "db_name": "MySQL", + "query": "\nSELECT\n api_keys.token as `api_key_token: ApiKeyToken`,\n users.token as `user_token: UserToken`,\n users.is_banned\nFROM api_keys\nINNER JOIN users\n ON users.token = api_keys.owner_user_token\nWHERE api_keys.api_key = ?\n AND api_keys.maybe_deleted_at IS NULL\nLIMIT 1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "api_key_token: ApiKeyToken", + "type_info": { + "type": "VarString", + "flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE", + "char_set": 224, + "max_size": 128 + } + }, + { + "ordinal": 1, + "name": "user_token: UserToken", + "type_info": { + "type": "VarString", + "flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE", + "char_set": 224, + "max_size": 128 + } + }, + { + "ordinal": 2, + "name": "is_banned", + "type_info": { + "type": "Tiny", + "flags": "NOT_NULL", + "char_set": 63, + "max_size": 1 + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "f52c495a7ff5cafb357f79c4cdb503bbbef045fd193a5a37a66180fcac77f363" +} diff --git a/crates/schema/database/mysql_queries/src/queries/api_keys/get_api_key_user_by_value.rs b/crates/schema/database/mysql_queries/src/queries/api_keys/get_api_key_user_by_value.rs new file mode 100644 index 0000000000..59b8202ee2 --- /dev/null +++ b/crates/schema/database/mysql_queries/src/queries/api_keys/get_api_key_user_by_value.rs @@ -0,0 +1,63 @@ +use std::marker::PhantomData; + +use sqlx::{Executor, MySql}; + +use artcraft_api_keys::ArtcraftApiKey; +use tokens::tokens::api_keys::ApiKeyToken; +use tokens::tokens::users::UserToken; + +use crate::helpers::boolean_converters::i8_to_bool; + +pub struct GetApiKeyUserByValueArgs<'e, 'c, E> +where + E: 'e + Executor<'c, Database = MySql>, +{ + /// The actual API key secret presented by the client. + pub api_key: &'e ArtcraftApiKey, + pub mysql_executor: E, + pub phantom: PhantomData<&'c E>, +} + +/// A live API key joined to its owning user. This is the minimal identity needed to authenticate +/// an API-key request — far smaller than a cookie `SessionUserRecord`. +pub struct ApiKeyUserRecord { + pub api_key_token: ApiKeyToken, + pub user_token: UserToken, + pub is_banned: bool, +} + +/// Look up the user behind an API key secret, for authenticating an incoming request. +/// +/// Returns `Ok(None)` when no row matches, when the matching API key is soft-deleted +/// (`maybe_deleted_at IS NOT NULL`), or when the owning user row does not exist (the `INNER JOIN` +/// requires both records to be present). The full secret is never echoed back. +pub async fn get_api_key_user_by_value<'e, 'c: 'e, E>( + args: GetApiKeyUserByValueArgs<'e, 'c, E>, +) -> Result, sqlx::Error> +where + E: 'e + Executor<'c, Database = MySql>, +{ + let result = sqlx::query!( + r#" +SELECT + api_keys.token as `api_key_token: ApiKeyToken`, + users.token as `user_token: UserToken`, + users.is_banned +FROM api_keys +INNER JOIN users + ON users.token = api_keys.owner_user_token +WHERE api_keys.api_key = ? + AND api_keys.maybe_deleted_at IS NULL +LIMIT 1 + "#, + args.api_key.as_str_be_careful(), + ) + .fetch_optional(args.mysql_executor) + .await?; + + Ok(result.map(|r| ApiKeyUserRecord { + api_key_token: r.api_key_token, + user_token: r.user_token, + is_banned: i8_to_bool(r.is_banned), + })) +} diff --git a/crates/schema/database/mysql_queries/src/queries/api_keys/mod.rs b/crates/schema/database/mysql_queries/src/queries/api_keys/mod.rs index 898cc84f80..deff03cc5b 100644 --- a/crates/schema/database/mysql_queries/src/queries/api_keys/mod.rs +++ b/crates/schema/database/mysql_queries/src/queries/api_keys/mod.rs @@ -1,6 +1,7 @@ pub mod api_key_row; pub mod delete_api_key; pub mod get_api_key_by_token; +pub mod get_api_key_user_by_value; pub mod get_live_api_key_by_value; pub mod insert_api_key; pub mod list_api_keys_for_user; 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 f15b9ca09d..2faf9fe2c9 100644 --- a/crates/service/web/storyteller_web/src/docs/api_doc.rs +++ b/crates/service/web/storyteller_web/src/docs/api_doc.rs @@ -550,6 +550,9 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux crate::http_server::endpoints::omni_gen::generate::video::omni_gen_video_generate_handler::omni_gen_video_generate_handler, crate::http_server::endpoints::omni_gen::models::image::omni_gen_image_models_handler::omni_gen_image_models_handler, crate::http_server::endpoints::omni_gen::models::video::omni_gen_video_models_handler::omni_gen_video_models_handler, + // Omni API (API-key authenticated generate) + crate::http_server::endpoints::omni_api::generate::image::omni_api_image_generate_handler::omni_api_image_generate_handler, + crate::http_server::endpoints::omni_api::generate::video::omni_api_video_generate_handler::omni_api_video_generate_handler, // Moderation crate::http_server::endpoints::moderation::alerts::moderation_send_alert_handler::moderation_send_alert_handler, crate::http_server::endpoints::moderation::info::moderator_token_info_handler::moderator_get_token_info_handler, diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/mod.rs index 4d593ba4d7..bc28bdeafe 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/mod.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/mod.rs @@ -17,6 +17,7 @@ pub mod media_files; pub mod misc; pub mod model_download; pub mod moderation; +pub mod omni_api; pub mod omni_gen; pub mod prompts; pub mod service; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/hydrate_to_router_request.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/hydrate_to_router_request.rs new file mode 100644 index 0000000000..566533f5b0 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/hydrate_to_router_request.rs @@ -0,0 +1,100 @@ +use artcraft_api_defs::omni_gen::cost_and_generate_requests::omni_gen_image_cost_and_generate_request::OmniGenImageCostAndGenerateRequest; +use artcraft_router::api::router_aspect_ratio::RouterAspectRatio; +use artcraft_router::api::router_image_model::RouterImageModel; +use artcraft_router::api::router_quality::RouterQuality; +use artcraft_router::api::router_resolution::RouterResolution; +use artcraft_router::api::image_list_ref::ImageListRef; +use artcraft_router::api::router_provider::RouterProvider; +use artcraft_router::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; +use artcraft_router::generate::generate_image::generate_image_request_builder::GenerateImageRequestBuilder; +use enums::common::generation::common_aspect_ratio::CommonAspectRatio as CommonAspectRatioEnum; +use enums::common::generation::common_image_model::CommonImageModel as CommonImageModelEnum; +use enums::common::generation::common_quality::CommonQuality as CommonQualityEnum; +use enums::common::generation::common_resolution::CommonResolution as CommonResolutionEnum; + +use crate::http_server::common_responses::common_web_error::CommonWebError; + +pub fn hydrate_to_router_request( + request: &OmniGenImageCostAndGenerateRequest, +) -> Result { + let api_model = request.model.as_ref() + .ok_or_else(|| CommonWebError::BadInputWithSimpleMessage( + "model is required".to_string(), + ))?; + + let model = convert_model(api_model)?; + + let aspect_ratio = request.aspect_ratio.as_ref() + .map(convert_aspect_ratio) + .transpose()?; + + let resolution = request.resolution.as_ref() + .map(convert_resolution) + .transpose()?; + + let quality = request.quality.as_ref() + .map(convert_quality) + .transpose()?; + + Ok(GenerateImageRequestBuilder { + model, + provider: RouterProvider::Artcraft, + prompt: request.prompt.clone(), + image_inputs: request.image_media_tokens.clone() + .map(ImageListRef::MediaFileTokens), + resolution, + aspect_ratio, + quality, + image_batch_count: request.image_batch_count, + horizontal_angle: request.adjust_horizontal_angle, + vertical_angle: request.adjust_vertical_angle, + zoom: request.adjust_zoom, + request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade, + generation_mode_mismatch_strategy: None, + idempotency_token: request.idempotency_token.clone(), + }) +} + +fn convert_model( + model: &CommonImageModelEnum, +) -> Result { + let json = serde_json::to_string(model)?; + serde_json::from_str(&json).map_err(|e| { + CommonWebError::BadInputWithSimpleMessage( + format!("Unsupported image model: {}", e), + ) + }) +} + +fn convert_aspect_ratio( + ar: &CommonAspectRatioEnum, +) -> Result { + let json = serde_json::to_string(ar)?; + serde_json::from_str(&json).map_err(|e| { + CommonWebError::BadInputWithSimpleMessage( + format!("Unsupported aspect ratio: {}", e), + ) + }) +} + +fn convert_resolution( + res: &CommonResolutionEnum, +) -> Result { + let json = serde_json::to_string(res)?; + serde_json::from_str(&json).map_err(|e| { + CommonWebError::BadInputWithSimpleMessage( + format!("Unsupported resolution: {}", e), + ) + }) +} + +fn convert_quality( + quality: &CommonQualityEnum, +) -> Result { + let json = serde_json::to_string(quality)?; + serde_json::from_str(&json).map_err(|e| { + CommonWebError::BadInputWithSimpleMessage( + format!("Unsupported quality: {}", e), + ) + }) +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/insert_db_job/insert_fal_job.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/insert_db_job/insert_fal_job.rs new file mode 100644 index 0000000000..cf27586134 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/insert_db_job/insert_fal_job.rs @@ -0,0 +1,61 @@ +use log::warn; + +use enums::common::visibility::Visibility; +use mysql_queries::queries::generic_inference::api_providers::fal::insert_generic_inference_job_for_fal_queue::FalCategory; +use mysql_queries::queries::generic_inference::api_providers::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{ + insert_generic_inference_job_for_fal_queue_with_apriori_job_token, + InsertGenericInferenceForFalWithAprioriJobTokenArgs, +}; +use tokens::tokens::generic_inference_jobs::InferenceJobToken; + +use crate::http_server::common_responses::common_web_error::CommonWebError; + +use super::shared_job_args::SharedJobArgs; + +pub struct InsertFalJobArgs<'a, 'tx> { + pub external_job_id: &'a str, + pub shared: SharedJobArgs<'a, 'tx>, +} + +/// Insert a single Fal-queue inference job row for an image generation. +/// Mirrors `omni_gen::generate::video::insert_db_job::insert_fal_job::insert_fal_job` +/// but tags the row with `FalCategory::ImageGeneration` so the worker +/// routes it to the image-generation queue. +pub async fn insert_fal_job(args: InsertFalJobArgs<'_, '_>) -> Result { + let InsertFalJobArgs { + external_job_id, + shared, + } = args; + + let db_result = insert_generic_inference_job_for_fal_queue_with_apriori_job_token( + InsertGenericInferenceForFalWithAprioriJobTokenArgs { + apriori_job_token: shared.apriori_job_token, + uuid_idempotency_token: shared.idempotency_token, + maybe_external_third_party_id: external_job_id, + fal_category: FalCategory::ImageGeneration, + maybe_model_type: shared.maybe_model_type, + maybe_prompt_token: shared.maybe_prompt_token, + maybe_creator_user_token: Some(shared.user_token), + maybe_avt_token: shared.maybe_avt_token, + creator_ip_address: shared.ip_address, + creator_set_visibility: Visibility::Public, + maybe_platform_type: shared.maybe_platform_type, + maybe_cost_estimates: None, + maybe_debug_log_event_token: shared.maybe_debug_log_event_token, + mysql_executor: &mut **shared.transaction, + maybe_inference_args: None, + starting_job_status_override: None, + maybe_frontend_failure_category: None, + maybe_failure_reason: None, + phantom: Default::default(), + } + ).await; + + match db_result { + Ok(token) => Ok(token), + Err(err) => { + warn!("Error inserting fal inference job (image): {:?}", err); + Err(CommonWebError::from_error(err)) + } + } +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/insert_db_job/insert_seedance2pro_jobs.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/insert_db_job/insert_seedance2pro_jobs.rs new file mode 100644 index 0000000000..05a87d6415 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/insert_db_job/insert_seedance2pro_jobs.rs @@ -0,0 +1,117 @@ +use log::{error, warn}; + +use enums::common::visibility::Visibility; +use mysql_queries::queries::generic_inference::api_providers::seedance2pro::insert_generic_inference_job_for_seedance2pro_queue_with_apriori_job_token::{ + insert_generic_inference_job_for_seedance2pro_queue_with_apriori_job_token, + InsertGenericInferenceForSeedance2ProWithAprioriJobTokenArgs, KinoviVersion, +}; +use tokens::tokens::generic_inference_jobs::InferenceJobToken; +use tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken; +use uuid_utils::uuid::generate_random_uuid; + +use crate::http_server::common_responses::common_web_error::CommonWebError; + +use super::shared_job_args::SharedJobArgs; + +pub struct InsertSeedance2proJobsArgs<'a, 'tx> { + /// The order_id for the primary job (always inserted first). + pub primary_order_id: &'a str, + + /// Additional order_ids returned by Kinovi when batch_count > 1. Each + /// becomes its own inference job row. + pub maybe_additional_order_ids: Option<&'a [String]>, + + /// Wallet ledger entry from the up-front deduction. Stamped on each + /// inserted row so the worker can issue a refund on failure. + pub maybe_wallet_ledger_entry_token: Option<&'a WalletLedgerEntryToken>, + + /// Which Kinovi account fulfilled the request — Volcengine, BytePlus, + /// or BytePlusUltra. For Midjourney image generation today this is + /// always Volcengine, but the helper stays version-agnostic so a future + /// account-picking layer can use it unchanged. + pub kinovi_version: KinoviVersion, + + pub shared: SharedJobArgs<'a, 'tx>, +} + +pub struct InsertSeedance2proJobsResult { + pub primary_job_token: InferenceJobToken, + pub all_job_tokens: Vec, +} + +/// Insert one Seedance2Pro/Kinovi inference job per returned order_id. +/// Mirrors the video-side helper at +/// `omni_gen::generate::video::insert_db_job::insert_seedance2pro_jobs`. +pub async fn insert_seedance2pro_jobs(args: InsertSeedance2proJobsArgs<'_, '_>) -> Result { + let InsertSeedance2proJobsArgs { + primary_order_id, + maybe_additional_order_ids, + maybe_wallet_ledger_entry_token, + kinovi_version, + mut shared, + } = args; + + // Build a deduplicated list with primary_order_id first. + let mut all_order_ids = vec![primary_order_id.to_string()]; + + if let Some(additional) = maybe_additional_order_ids { + for id in additional { + if id != primary_order_id { + all_order_ids.push(id.clone()); + } + } + } + + let mut all_job_tokens: Vec = Vec::with_capacity(all_order_ids.len()); + + for (i, order_id) in all_order_ids.iter().enumerate() { + let job_token = if i == 0 { shared.apriori_job_token.clone() } else { InferenceJobToken::generate() }; + + // The idempotency_token column has a max length too tight for the + // `{idempotency}-batch-{i}` suffix pattern used elsewhere; mint a + // fresh UUID for each additional row instead. The primary row still + // wants a deterministic value, but historic behavior on the video + // side has been to mint UUIDs for all rows, which we mirror here. + let idempotency_str = generate_random_uuid(); + + let db_result = insert_generic_inference_job_for_seedance2pro_queue_with_apriori_job_token( + InsertGenericInferenceForSeedance2ProWithAprioriJobTokenArgs { + kinovi_version, + apriori_job_token: &job_token, + uuid_idempotency_token: &idempotency_str, + maybe_external_third_party_id: order_id, + maybe_model_type: shared.maybe_model_type, + maybe_prompt_token: shared.maybe_prompt_token, + maybe_wallet_ledger_entry_token, + maybe_creator_user_token: Some(shared.user_token), + maybe_avt_token: shared.maybe_avt_token, + creator_ip_address: shared.ip_address, + creator_set_visibility: Visibility::Public, + maybe_platform_type: shared.maybe_platform_type, + maybe_cost_estimates: None, + maybe_debug_log_event_token: shared.maybe_debug_log_event_token, + mysql_executor: &mut **shared.transaction, + maybe_inference_args: None, + phantom: Default::default(), + } + ).await; + + match db_result { + Ok(token) => all_job_tokens.push(token), + Err(err) => { + warn!("Error inserting seedance2pro inference job (image, order_id={}): {:?}", order_id, err); + if i == 0 { return Err(CommonWebError::from_error(err)); } + } + } + } + + let primary_job_token = all_job_tokens.first().cloned().ok_or_else(|| { + error!("No inference job token was created"); + CommonWebError::server_error_with_message("No inference job token was created") + })?; + + Ok(InsertSeedance2proJobsResult { + primary_job_token, + all_job_tokens, + }) +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/insert_db_job/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/insert_db_job/mod.rs new file mode 100644 index 0000000000..751cbf87e7 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/insert_db_job/mod.rs @@ -0,0 +1,3 @@ +pub mod insert_fal_job; +pub mod insert_seedance2pro_jobs; +pub mod shared_job_args; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/insert_db_job/shared_job_args.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/insert_db_job/shared_job_args.rs new file mode 100644 index 0000000000..e83ff66c7d --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/insert_db_job/shared_job_args.rs @@ -0,0 +1,22 @@ +use enums::common::generation::common_model_type::CommonModelType; +use enums::common::platform_type::PlatformType; +use tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken; +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; + +/// Fields every image-side `insert_*_job` helper needs. +/// Parallels `omni_gen::generate::video::insert_db_job::shared_job_args::SharedJobArgs`. +pub struct SharedJobArgs<'a, 'tx> { + pub apriori_job_token: &'a InferenceJobToken, + pub idempotency_token: &'a str, + pub user_token: &'a UserToken, + pub maybe_avt_token: Option<&'a AnonymousVisitorTrackingToken>, + pub maybe_prompt_token: Option<&'a PromptToken>, + pub maybe_debug_log_event_token: Option<&'a DebugLogEventToken>, + pub maybe_model_type: Option, + pub maybe_platform_type: Option, + pub ip_address: &'a str, + pub transaction: &'a mut sqlx::Transaction<'tx, sqlx::MySql>, +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/mod.rs new file mode 100644 index 0000000000..2b127f5225 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/mod.rs @@ -0,0 +1,5 @@ +pub mod omni_api_image_generate_handler; +pub(crate) mod hydrate_to_router_request; +pub(crate) mod insert_db_job; +pub(crate) mod pipeline_result; +pub mod pipeline_v2; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/omni_api_image_generate_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/omni_api_image_generate_handler.rs new file mode 100644 index 0000000000..1468e5aa9d --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/omni_api_image_generate_handler.rs @@ -0,0 +1,313 @@ +use std::sync::Arc; + +use actix_web::web::Json; +use actix_web::{web, HttpRequest}; +use log::{error, info, warn}; +use sqlx::Acquire; + +use artcraft_api_defs::omni_gen::cost_and_generate_requests::omni_gen_image_cost_and_generate_request::OmniGenImageCostAndGenerateRequest; +use artcraft_api_defs::omni_gen::generate_response::omni_gen_image_generate_response::OmniGenImageGenerateResponse; +use artcraft_router::generate::generate_image::generate_image_response::GenerateImageResponse; +use enums::by_table::debug_logs::debug_log_type::DebugLogType; +use enums::by_table::prompt_context_items::prompt_context_semantic_type::PromptContextSemanticType; +use enums::by_table::prompts::prompt_type::PromptType; +use enums::common::generation::common_generation_mode::CommonGenerationMode; +use enums::common::generation::common_model_type::CommonModelType; +use enums::common::generation_provider::GenerationProvider; +use http_server_common::request::get_request_ip::get_request_ip; +use mysql_queries::queries::debug_logs::insert_debug_log::{insert_debug_log, InsertDebugLogArgs}; +use mysql_queries::queries::generic_inference::api_providers::seedance2pro::insert_generic_inference_job_for_seedance2pro_queue_with_apriori_job_token::KinoviVersion; +use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token; +use mysql_queries::queries::prompt_context_items::insert_batch_prompt_context_items::{ + insert_batch_prompt_context_items, InsertBatchArgs, PromptContextItem, +}; +use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPromptArgs}; +use tokens::tokens::generic_inference_jobs::InferenceJobToken; +use tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken; + +use crate::http_server::common_responses::common_web_error::CommonWebError; +use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test; +use crate::http_server::endpoints::omni_api::generate::image::hydrate_to_router_request::hydrate_to_router_request; +use crate::http_server::endpoints::omni_api::generate::image::insert_db_job::insert_fal_job::{insert_fal_job, InsertFalJobArgs}; +use crate::http_server::endpoints::omni_api::generate::image::insert_db_job::insert_seedance2pro_jobs::{insert_seedance2pro_jobs, InsertSeedance2proJobsArgs}; +use crate::http_server::endpoints::omni_api::generate::image::insert_db_job::shared_job_args::SharedJobArgs; +use crate::http_server::endpoints::omni_api::generate::image::pipeline_v2::run_pipeline_v2::{run_pipeline_v2, RunPipelineV2Args}; +use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; +use crate::http_server::user_lookup::api_keys::require_api_key_user::require_api_key_user; +use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; +use crate::http_server::web_utils::get_request_platform_type::get_request_platform_type; +use crate::state::server_state::ServerState; +use crate::util::lookup::lookup_media_files_as_cdn_url_list_and_map::lookup_media_files_as_cdn_url_list_and_map; + +/// Generate an image using the omni-api unified endpoint (API-key authenticated). +#[utoipa::path( + post, + tag = "Omni API", + path = "/v1/omni_api/generate/image", + request_body = OmniGenImageCostAndGenerateRequest, + responses( + (status = 200, description = "Success", body = OmniGenImageGenerateResponse), + (status = 400, description = "Bad input"), + (status = 401, description = "Unauthorized"), + (status = 402, description = "Payment required"), + (status = 500, description = "Server error"), + ), +)] +pub async fn omni_api_image_generate_handler( + http_request: HttpRequest, + request: Json, + server_state: web::Data>, +) -> Result, CommonWebError> { + + info!("request: {:?}", request); + + payments_error_test(&request.prompt.as_deref().unwrap_or(""))?; + + let debug_log_event_token = DebugLogEventToken::generate(); + + let maybe_prompt_model_type: Option = request.model + .as_ref() + .map(|m| m.to_common_model_type()); + + // ==================== API KEY USER ==================== // + + let mut mysql_connection = server_state.mysql_pool.acquire().await?; + + // API-key authentication (Authorization header) instead of a session cookie. Never cached. + let api_session = require_api_key_user(&http_request, &mut *mysql_connection).await?; + + let user_token = &api_session.user_token; + + let maybe_avt_token = server_state + .avt_cookie_manager + .get_avt_token_from_request(&http_request); + + // ==================== MODEL ACCESS CHECK ==================== // + + // API-key users have no per-user feature flags today. + let user_feature_flags = UserSessionFeatureFlags::new(None); + + // ==================== IDEMPOTENCY ==================== // + + let idempotency_token = request.idempotency_token.as_deref() + .unwrap_or("") + .to_string(); + + if let Err(reason) = validate_idempotency_token_format(&idempotency_token) { + return Err(CommonWebError::BadInputWithSimpleMessage(reason)); + } + + insert_idempotency_token(&idempotency_token, &mut *mysql_connection) + .await + .map_err(|err| { + error!("Error inserting idempotency token: {:?}", err); + CommonWebError::BadInputWithSimpleMessage("repeated idempotency token".to_string()) + })?; + + // ==================== RESOLVE MEDIA TOKENS ==================== // + + // Look up media file tokens BEFORE distilling. Pipeline execution should not do I/O. + let resolved_media = lookup_media_files_as_cdn_url_list_and_map( + &http_request, + &mut mysql_connection, + server_state.server_environment, + request.image_media_tokens.as_deref().unwrap_or(&[]), + ).await?; + + // ==================== HYDRATE ROUTER REQUEST ==================== // + + let router_builder = hydrate_to_router_request(&request)?; + + // ==================== DEBUG LOG: HTTP REQUEST ==================== // + + if let Err(err) = insert_debug_log(InsertDebugLogArgs { + apriori_debug_log_event_token: Some(&debug_log_event_token), + maybe_creator_user_token: Some(user_token), + debug_log_type: DebugLogType::HttpRequest, + message: &serde_json::to_string(&*request).unwrap_or_default(), + mysql_executor: &mut *mysql_connection, + phantom: Default::default(), + }).await { + warn!("Failed to insert HTTP request debug log: {:?}", err); + } + + // ==================== PIPELINE ==================== // + + // NB: Release the pooled DB connection before the (slow, external) generation call so we don't + // hold a pool slot idle while waiting on the provider — that's what starves the pool and causes + // PoolTimedOut on unrelated endpoints. We re-acquire below to write the result. + drop(mysql_connection); + + let pipeline_result = run_pipeline_v2(RunPipelineV2Args { + router_builder: &router_builder, + server_state: &server_state, + user_token, + resolved_media: &resolved_media, + }).await?; + + let mut mysql_connection = server_state.mysql_pool.acquire().await?; + + // ==================== DEBUG LOG: FAL REQUEST ==================== // + + if let GenerateImageResponse::Fal(ref fal_payload) = pipeline_result.response { + if let Some(ref outbound_request) = fal_payload.maybe_outbound_request { + if let Err(err) = insert_debug_log(InsertDebugLogArgs { + apriori_debug_log_event_token: Some(&debug_log_event_token), + maybe_creator_user_token: Some(user_token), + debug_log_type: DebugLogType::FalRequest, + message: &format!("{:#?}", outbound_request), + mysql_executor: &mut *mysql_connection, + phantom: Default::default(), + }).await { + warn!("Failed to insert Fal request debug log: {:?}", err); + } + } + } + + // ==================== WRITE RESULT ==================== // + + let ip_address = get_request_ip(&http_request); + let maybe_platform_type = get_request_platform_type(&http_request); + + let mut transaction = mysql_connection + .begin() + .await + .map_err(|err| { + error!("Error starting MySQL transaction: {:?}", err); + CommonWebError::from_error(err) + })?; + + // -- Prompt -- + + let generation_mode = if request.image_media_tokens.is_some() { + CommonGenerationMode::Edit + } else { + CommonGenerationMode::Text + }; + + let prompt_result = insert_prompt(InsertPromptArgs { + maybe_apriori_prompt_token: None, + prompt_type: PromptType::ArtcraftApp, + maybe_creator_user_token: Some(user_token), + maybe_model_type: maybe_prompt_model_type, + maybe_generation_provider: Some(GenerationProvider::Artcraft), + maybe_positive_prompt: request.prompt.as_deref(), + maybe_negative_prompt: None, + maybe_other_args: None, + maybe_generation_mode: Some(generation_mode), + maybe_aspect_ratio: request.aspect_ratio, // TODO: should be saved from router's decision as it could have changed + maybe_resolution: request.resolution,// TODO: should be saved from router's decision as it could have changed + maybe_batch_count: request.image_batch_count.map(|c| c as u8), + maybe_generate_audio: None, // NB: Images, not video + maybe_duration_seconds: None, // NB: Images, not video + creator_ip_address: &ip_address, + mysql_executor: &mut *transaction, + phantom: Default::default(), + }).await; + + let prompt_token = match prompt_result { + Ok(token) => Some(token), + Err(err) => { + warn!("Error inserting prompt: {:?}", err); + None + } + }; + + // -- Prompt context items -- + + if let Some(token) = prompt_token.as_ref() { + let mut context_items = Vec::new(); + + if let Some(ref_tokens) = &request.image_media_tokens { + for media_token in ref_tokens { + context_items.push(PromptContextItem { + media_token: media_token.clone(), + context_semantic_type: PromptContextSemanticType::Imgref, + }); + } + } + + if !context_items.is_empty() { + if let Err(err) = insert_batch_prompt_context_items(InsertBatchArgs { + prompt_token: token.clone(), + items: context_items, + transaction: &mut transaction, + }).await { + warn!("Error inserting batch prompt context items: {:?}", err); + } + } + } + + // -- Inference job -- + // + // Each provider has its own queue / worker. Branch by response variant + // so the row lands on the correct queue (the Seedance2Pro/Kinovi worker + // for Midjourney; the Fal worker for everything else). + + let job_token: InferenceJobToken = match &pipeline_result.response { + GenerateImageResponse::Seedance2Pro(payload) => { + info!("Inserting seedance2pro image job(s) with token: {:?}", pipeline_result.apriori_job_token); + + // The image-side omni pipeline always dispatches Midjourney via the + // Volcengine Kinovi account today. If we ever route to BytePlus / + // BytePlus Ultra here, mirror the video-side `kinovi_account` knob. + let kinovi_version = KinoviVersion::Volcengine; + + let result = insert_seedance2pro_jobs(InsertSeedance2proJobsArgs { + primary_order_id: &payload.order_id, + maybe_additional_order_ids: payload.maybe_order_ids.as_deref(), + maybe_wallet_ledger_entry_token: pipeline_result.maybe_wallet_ledger_entry_token.as_ref(), + kinovi_version, + shared: SharedJobArgs { + apriori_job_token: &pipeline_result.apriori_job_token, + idempotency_token: &idempotency_token, + user_token, + maybe_avt_token: maybe_avt_token.as_ref(), + maybe_model_type: request.model.map(|v| v.to_common_model_type()), + maybe_prompt_token: prompt_token.as_ref(), + maybe_debug_log_event_token: Some(&debug_log_event_token), + maybe_platform_type, + ip_address: &ip_address, + transaction: &mut transaction, + }, + }).await?; + result.primary_job_token + } + GenerateImageResponse::Fal(payload) => { + info!("Inserting fal image job with token: {:?}", pipeline_result.apriori_job_token); + let external_job_id = payload.request_id.clone().unwrap_or_default(); + insert_fal_job(InsertFalJobArgs { + external_job_id: &external_job_id, + shared: SharedJobArgs { + apriori_job_token: &pipeline_result.apriori_job_token, + idempotency_token: &idempotency_token, + user_token, + maybe_avt_token: maybe_avt_token.as_ref(), + maybe_model_type: request.model.map(|v| v.to_common_model_type()), + maybe_prompt_token: prompt_token.as_ref(), + maybe_debug_log_event_token: Some(&debug_log_event_token), + maybe_platform_type, + ip_address: &ip_address, + transaction: &mut transaction, + }, + }).await? + } + GenerateImageResponse::Artcraft(payload) => { + // The omni image pipeline never dispatches via the Artcraft provider + // itself today (everything routes to Fal or Kinovi), but the response + // variant exists so we cover it defensively — Artcraft jobs come back + // already inserted server-side, so just propagate the token. + payload.inference_job_token.clone() + } + }; + + transaction.commit().await.map_err(|err| { + error!("Error committing transaction: {:?}", err); + CommonWebError::from_error(err) + })?; + + Ok(Json(OmniGenImageGenerateResponse { + success: true, + inference_job_token: job_token, + })) +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/pipeline_result.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/pipeline_result.rs new file mode 100644 index 0000000000..4d4bd9120d --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/pipeline_result.rs @@ -0,0 +1,15 @@ +use artcraft_router::generate::generate_image::generate_image_response::GenerateImageResponse; +use tokens::tokens::generic_inference_jobs::InferenceJobToken; +use tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken; + +/// Both image pipelines produce this, then the shared handler suffix writes DB rows. +pub struct ImagePipelineResult { + pub apriori_job_token: InferenceJobToken, + pub response: GenerateImageResponse, + + /// The wallet ledger entry created when the user was charged for this + /// generation, if any (None when the generation is free). Used as a + /// foreign key on the inference job row so the worker can refund on + /// failure. + pub maybe_wallet_ledger_entry_token: Option, +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/pipeline_v2/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/pipeline_v2/mod.rs new file mode 100644 index 0000000000..f207b21293 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/pipeline_v2/mod.rs @@ -0,0 +1 @@ +pub mod run_pipeline_v2; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/pipeline_v2/run_pipeline_v2.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/pipeline_v2/run_pipeline_v2.rs new file mode 100644 index 0000000000..eb9dd59ed5 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/pipeline_v2/run_pipeline_v2.rs @@ -0,0 +1,208 @@ +use log::{info, warn}; + +use artcraft_router::api::image_list_ref::ImageListRef; +use artcraft_router::api::router_image_model::RouterImageModel; +use artcraft_router::api::router_provider::RouterProvider; +use artcraft_router::client::router_client::RouterClient; +use artcraft_router::client::router_fal_client::RouterFalClient; +use artcraft_router::client::router_seedance2pro_client::RouterSeedance2ProClient; +use artcraft_router::generate::generate_image::generate_image_request_builder::GenerateImageRequestBuilder; +use artcraft_router::generate::generate_image::generate_image_response::GenerateImageResponse; +use artcraft_router::generate::generate_image::image_generation_draft_context::ImageGenerationDraftContext; +use artcraft_router::generate::generate_image::image_generation_draft_or_request::ImageGenerationDraftOrRequest; +use artcraft_router::generate::generate_image::image_generation_request::ImageGenerationRequest; +use seedance2pro_client::creds::seedance2pro_session::Seedance2ProSession; +use tokens::tokens::generic_inference_jobs::InferenceJobToken; +use tokens::tokens::users::UserToken; + +use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error; +use crate::http_server::common_responses::common_web_error::CommonWebError; +use crate::http_server::endpoints::omni_api::generate::image::pipeline_result::ImagePipelineResult; +use crate::http_server::endpoints::omni_api::shared_utils::map_seedance2pro_router_error::map_router_error_to_web_error; +use crate::state::server_state::ServerState; +use crate::util::lookup::lookup_media_files_as_cdn_url_list_and_map::MediaFilesAsCdnUrlListAndMap; + +pub struct RunPipelineV2Args<'a> { + pub router_builder: &'a GenerateImageRequestBuilder, + pub server_state: &'a ServerState, + pub user_token: &'a UserToken, + pub resolved_media: &'a MediaFilesAsCdnUrlListAndMap, +} + +// NB: This pipeline does an external generation call (`finalize_and_generate`) that can take many +// seconds. It deliberately does NOT hold a pooled DB connection across that call — it acquires a +// short-lived connection only for the wallet deduction. Holding a pooled connection across the +// external call is what starves the pool and causes `PoolTimedOut`. +pub async fn run_pipeline_v2( + args: RunPipelineV2Args<'_>, +) -> Result { + let RunPipelineV2Args { + router_builder, + server_state, + user_token, + resolved_media, + } = args; + + let hydrated_builder = apply_hydrated_media_inputs( + router_builder, + resolved_media, + ); + + let cost = estimate_cost_in_credits(&router_builder)?; + + let draft_or_request = build_execution_request(&hydrated_builder)?; + + info!("Charging wallet: {} credits", cost); + + let apriori_job_token = InferenceJobToken::generate(); + + let maybe_wallet_ledger_entry_token = if cost > 0 { + // NB: Short-lived connection — released before the external call below. + let mut billing_connection = server_state.mysql_pool.acquire().await + .map_err(|err| { + warn!("Failed to acquire MySQL connection for image billing: {:?}", err); + CommonWebError::from_error(err) + })?; + let deduction = attempt_wallet_deduction_else_common_web_error( + user_token, + Some(apriori_job_token.as_str()), + cost, + &mut billing_connection, + ).await?; + Some(deduction.ledger_entry_token) + } else { + None + }; + + // NB: No pooled DB connection is held across this external generation call. + let response = finalize_and_generate(draft_or_request, server_state, resolved_media).await?; + + Ok(ImagePipelineResult { + apriori_job_token, + response, + maybe_wallet_ledger_entry_token, + }) +} + +fn build_execution_request( + router_builder: &GenerateImageRequestBuilder, +) -> Result { + let mut execution_builder = router_builder.clone(); + execution_builder.provider = provider_for_model(execution_builder.model); + + execution_builder.build2().map_err(|e| { + warn!("Failed to build2 for image v2 pipeline: {}", e); + CommonWebError::from_error(e) + }) +} + +/// Route each image model to the provider that fulfils it. Midjourney is +/// served via the Seedance2Pro/Kinovi (Volcengine) backend; everything else +/// flows through Fal. +fn provider_for_model(model: RouterImageModel) -> RouterProvider { + match model { + RouterImageModel::Midjourney7 + | RouterImageModel::Midjourney7Niji + | RouterImageModel::Midjourney8 => RouterProvider::Seedance2Pro, + _ => RouterProvider::Fal, + } +} + +fn apply_hydrated_media_inputs( + router_builder: &GenerateImageRequestBuilder, + resolved_media: &MediaFilesAsCdnUrlListAndMap, +) -> GenerateImageRequestBuilder { + let mut hydrated_builder = router_builder.clone(); + + match hydrated_builder.image_inputs.as_ref() { + Some(ImageListRef::MediaFileTokens(tokens)) if !tokens.is_empty() => { + hydrated_builder.image_inputs = Some(ImageListRef::Urls( + resolved_media.ordered_url_list.clone(), + )); + }, + _ => {}, + } + + hydrated_builder +} + +fn estimate_cost_in_credits( + router_builder: &GenerateImageRequestBuilder, +) -> Result { + let mut cost_builder = router_builder.clone(); + cost_builder.provider = RouterProvider::Artcraft; + + let request = cost_builder.build2().map_err(|e| { + warn!("Failed to build image cost request: {}", e); + CommonWebError::from_error(e) + })?; + + let cost = request.estimate_cost().map_err(|e| { + warn!("Failed to estimate image cost: {}", e); + CommonWebError::from_error(e) + })?; + + Ok(cost.cost_in_credits.unwrap_or(0)) +} + +async fn finalize_and_generate( + draft_or_request: ImageGenerationDraftOrRequest, + server_state: &ServerState, + resolved_media: &MediaFilesAsCdnUrlListAndMap, +) -> Result { + let provider = draft_or_request.get_provider(); + let client = build_router_client(provider, server_state)?; + + // Inline the draft finalization so the draft context can borrow `client`. + let request: ImageGenerationRequest = match draft_or_request { + ImageGenerationDraftOrRequest::Request(request) => request, + ImageGenerationDraftOrRequest::Draft(draft) => { + let draft_context = ImageGenerationDraftContext { + client: Some(&client), + media_file_to_artcraft_url_map: Some(&resolved_media.token_to_url_map), + }; + draft.finalize(draft_context) + .await + .map_err(|err| { + warn!("Failed to finalize image v2 draft: {:?}", err); + map_router_error_to_web_error(err) + })? + } + }; + + request.send_request(&client) + .await + .map_err(|err| { + warn!("v2 image generation failed: {:?}", err); + map_router_error_to_web_error(err) + }) +} + +fn build_router_client( + provider: RouterProvider, + server_state: &ServerState, +) -> Result { + match provider { + RouterProvider::Fal => { + let fal_client = RouterFalClient::new_with_webhook( + server_state.inference_providers.fal.api_key.clone(), + server_state.inference_providers.fal.webhook_url.clone(), + ); + Ok(RouterClient::Fal(fal_client)) + }, + RouterProvider::Seedance2Pro => { + // Midjourney image generation always uses the Volcengine account. + // Per-request account selection (like the video flow's + // `KinoviAccount` enum) isn't needed here yet — if it ever is, plumb + // a parallel knob through `RunPipelineV2Args`. + let cookies = server_state.inference_providers.seedance2pro.cookies_volcengine.clone(); + let session = Seedance2ProSession::from_cookies_string(cookies); + Ok(RouterClient::Seedance2Pro(RouterSeedance2ProClient::new(session))) + }, + other => { + Err(CommonWebError::server_error_with_message( + &format!("Unsupported provider for image v2 generation: {:?}", other), + )) + }, + } +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/mod.rs new file mode 100644 index 0000000000..c10a6ca15e --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/mod.rs @@ -0,0 +1,2 @@ +pub mod image; +pub mod video; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/helpers/bill_wallet.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/helpers/bill_wallet.rs new file mode 100644 index 0000000000..4529d92d71 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/helpers/bill_wallet.rs @@ -0,0 +1,44 @@ +//! Shared wallet billing logic for both video generation pipelines. + +use log::info; +use tokens::tokens::generic_inference_jobs::InferenceJobToken; +use tokens::tokens::users::UserToken; +use tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken; + +use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error; +use crate::http_server::common_responses::common_web_error::CommonWebError; + +pub struct BillWalletResult { + pub apriori_job_token: InferenceJobToken, + pub maybe_wallet_ledger_entry_token: Option, +} + +/// Generate an apriori job token and bill the user's wallet for the given cost. +/// +/// If cost is 0, no wallet deduction is made but the apriori token is still generated. +pub async fn bill_wallet( + user_token: &UserToken, + cost: u64, + mysql_connection: &mut sqlx::pool::PoolConnection, +) -> Result { + let apriori_job_token = InferenceJobToken::generate(); + + info!("Charging wallet: {} credits", cost); + + let maybe_wallet_ledger_entry_token = if cost > 0 { + let deduction_result = attempt_wallet_deduction_else_common_web_error( + user_token, + Some(apriori_job_token.as_str()), + cost, + mysql_connection, + ).await?; + Some(deduction_result.ledger_entry_token) + } else { + None + }; + + Ok(BillWalletResult { + apriori_job_token, + maybe_wallet_ledger_entry_token, + }) +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/helpers/build_router_client.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/helpers/build_router_client.rs new file mode 100644 index 0000000000..0e153c7cb8 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/helpers/build_router_client.rs @@ -0,0 +1,61 @@ +//! Build a RouterClient for the given provider from server state. + +use artcraft_router::api::router_provider::RouterProvider; +use artcraft_router::client::router_client::RouterClient; +use artcraft_router::client::router_fal_client::RouterFalClient; +use artcraft_router::client::router_gmicloud_client::RouterGmiCloudClient; +use artcraft_router::client::router_grok_api_client::RouterGrokApiClient; +use artcraft_router::client::router_seedance2pro_client::RouterSeedance2ProClient; +use seedance2pro_client::creds::seedance2pro_session::Seedance2ProSession; + +use crate::http_server::common_responses::common_web_error::CommonWebError; +use crate::http_server::endpoints::omni_api::generate::video::kinovi_account::KinoviAccount; +use crate::state::server_state::ServerState; + +pub fn build_router_client( + provider: RouterProvider, + server_state: &ServerState, + kinovi_account: KinoviAccount, +) -> Result { + match provider { + RouterProvider::Seedance2Pro => { + kinovi_provider(server_state, kinovi_account) + } + RouterProvider::Fal => { + let fal_client = RouterFalClient::new_with_webhook( + server_state.inference_providers.fal.api_key.clone(), + server_state.inference_providers.fal.webhook_url.clone(), + ); + Ok(RouterClient::Fal(fal_client)) + } + RouterProvider::GmiCloud => { + Ok(RouterClient::GmiCloud(RouterGmiCloudClient::new( + server_state.inference_providers.gmicloud.api_key.clone(), + ))) + } + RouterProvider::GrokApi => { + Ok(RouterClient::GrokApi(RouterGrokApiClient::new( + server_state.inference_providers.grok_api.api_key.clone(), + ))) + } + other => { + Err(CommonWebError::server_error_with_message( + &format!("Unsupported provider for video generation: {:?}", other), + )) + } + } +} + +fn kinovi_provider(server_state: &ServerState, kinovi_account: KinoviAccount) -> Result { + let seedance2pro = &server_state.inference_providers.seedance2pro; + + let cookies = match kinovi_account { + KinoviAccount::Volcengine => seedance2pro.cookies_volcengine.clone(), + KinoviAccount::BytePlus => seedance2pro.cookies_byteplus.clone(), + KinoviAccount::BytePlusUltra => seedance2pro.cookies_byteplus_ultra.clone(), + }; + + let session = Seedance2ProSession::from_cookies_string(cookies); + + Ok(RouterClient::Seedance2Pro(RouterSeedance2ProClient::new(session))) +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/helpers/hydrate_router_request.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/helpers/hydrate_router_request.rs new file mode 100644 index 0000000000..604697dc0e --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/helpers/hydrate_router_request.rs @@ -0,0 +1,97 @@ +use crate::http_server::common_responses::common_web_error::CommonWebError; +use artcraft_api_defs::omni_gen::cost_and_generate_requests::omni_gen_video_cost_and_generate_request::OmniGenVideoCostAndGenerateRequest; +use artcraft_router::api::audio_list_ref::AudioListRef; +use artcraft_router::api::character_list_ref::CharacterListRef; +use artcraft_router::api::router_aspect_ratio::RouterAspectRatio; +use artcraft_router::api::router_resolution::RouterResolution; +use artcraft_router::api::router_video_model::RouterVideoModel; +use artcraft_router::api::image_list_ref::ImageListRef; +use artcraft_router::api::image_ref::ImageRef; +use artcraft_router::api::router_provider::RouterProvider; +use artcraft_router::api::video_list_ref::VideoListRef; +use artcraft_router::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; +use artcraft_router::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; +use enums::common::generation::common_aspect_ratio::CommonAspectRatio as CommonAspectRatioEnum; +use enums::common::generation::common_resolution::CommonResolution as CommonResolutionEnum; +use enums::common::generation::common_video_model::CommonVideoModel as CommonVideoModelEnum; + +pub fn hydrate_to_router_request( + request: &OmniGenVideoCostAndGenerateRequest, +) -> Result { + let api_model = request.model + .as_ref() + .ok_or_else(|| CommonWebError::BadInputWithSimpleMessage( + "model is required".to_string(), + ))?; + + let model = convert_model(api_model)?; + + let aspect_ratio = request.aspect_ratio + .as_ref() + .map(convert_aspect_ratio) + .transpose()?; + + let resolution = request.resolution + .as_ref() + .map(convert_resolution) + .transpose()?; + + Ok(GenerateVideoRequestBuilder { + model, + provider: RouterProvider::Artcraft, + prompt: request.prompt.clone(), + negative_prompt: request.negative_prompt.clone(), + start_frame: request.start_frame_image_media_token.clone() + .map(ImageRef::MediaFileToken), + end_frame: request.end_frame_image_media_token.clone() + .map(ImageRef::MediaFileToken), + reference_images: request.reference_image_media_tokens.clone() + .map(ImageListRef::MediaFileTokens), + reference_videos: request.reference_video_media_tokens.clone() + .map(VideoListRef::MediaFileTokens), + reference_audio: request.reference_audio_media_tokens.clone() + .map(AudioListRef::MediaFileTokens), + reference_character_tokens: request.reference_character_tokens.clone() + .map(CharacterListRef::CharacterTokens), + resolution, + aspect_ratio, + duration_seconds: request.duration_seconds, + video_batch_count: request.video_batch_count, + generate_audio: request.generate_audio, + request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade, + idempotency_token: request.idempotency_token.clone(), + }) +} + +fn convert_model( + model: &CommonVideoModelEnum, +) -> Result { + let json = serde_json::to_string(model)?; + serde_json::from_str(&json).map_err(|e| { + CommonWebError::BadInputWithSimpleMessage( + format!("Unsupported video model: {}", e), + ) + }) +} + +fn convert_aspect_ratio( + ar: &CommonAspectRatioEnum, +) -> Result { + let json = serde_json::to_string(ar)?; + serde_json::from_str(&json).map_err(|e| { + CommonWebError::BadInputWithSimpleMessage( + format!("Unsupported aspect ratio: {}", e), + ) + }) +} + +fn convert_resolution( + res: &CommonResolutionEnum, +) -> Result { + let json = serde_json::to_string(res)?; + serde_json::from_str(&json).map_err(|e| { + CommonWebError::BadInputWithSimpleMessage( + format!("Unsupported resolution: {}", e), + ) + }) +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/helpers/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/helpers/mod.rs new file mode 100644 index 0000000000..7108f22a3b --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/helpers/mod.rs @@ -0,0 +1,6 @@ +pub mod bill_wallet; +pub mod build_router_client; +pub(crate) mod hydrate_router_request; +pub mod pipeline_result; +pub mod resolve_kinovi_character_ids; +pub mod resolve_media_tokens_to_urls; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/helpers/pipeline_result.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/helpers/pipeline_result.rs new file mode 100644 index 0000000000..cb0648f34c --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/helpers/pipeline_result.rs @@ -0,0 +1,14 @@ +use artcraft_router::generate::generate_video::generate_video_response::GenerateVideoResponse; +use mysql_queries::queries::generic_inference::common::job_cost_estimates::JobCostEstimates; + +use crate::http_server::endpoints::omni_api::generate::video::helpers::bill_wallet::BillWalletResult; + +/// Both pipelines produce this, then the shared suffix handles DB writes. +pub struct PipelineResult { + pub billing: BillWalletResult, + pub response: GenerateVideoResponse, + + /// System (user-facing) and provider-side cost estimates, written onto the + /// generic_inference_jobs rows. + pub cost_estimates: JobCostEstimates, +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/helpers/resolve_kinovi_character_ids.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/helpers/resolve_kinovi_character_ids.rs new file mode 100644 index 0000000000..25b0f8aeac --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/helpers/resolve_kinovi_character_ids.rs @@ -0,0 +1,51 @@ +//! Resolve character tokens to Kinovi character IDs for prompting. + +use std::collections::HashMap; + +use log::warn; +use sqlx::MySql; + +use mysql_queries::queries::characters::batch_lookup_characters_by_token_for_prompting::batch_lookup_characters_by_token_for_prompting; +use tokens::tokens::characters::CharacterToken; + +use crate::http_server::common_responses::common_web_error::CommonWebError; + +/// Resolve character tokens to a map of CharacterToken → Kinovi character ID. +/// +/// Looks up the characters, filters to active ones with kinovi IDs, and warns about +/// any that are missing or inactive (but doesn't fail the request). +pub async fn resolve_kinovi_character_ids( + maybe_tokens: Option<&[CharacterToken]>, + connection: &mut sqlx::pool::PoolConnection, +) -> Result>, CommonWebError> { + let tokens = match maybe_tokens { + None => return Ok(None), + Some(tokens) if tokens.is_empty() => return Ok(None), + Some(tokens) => tokens, + }; + + let characters = batch_lookup_characters_by_token_for_prompting(tokens, connection) + .await?; + + if characters.len() != tokens.len() { + warn!( + "Not all character tokens were found: requested {}, found {}", + tokens.len(), characters.len(), + ); + } + + for character in &characters { + if !character.is_active { + warn!("Character {} is not yet active, skipping", character.token); + } + } + + let map: HashMap = characters.iter() + .filter(|c| c.is_active) + .filter_map(|c| { + c.kinovi_character_id.as_ref().map(|kid| (c.token.clone(), kid.clone())) + }) + .collect(); + + if map.is_empty() { Ok(None) } else { Ok(Some(map)) } +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/helpers/resolve_media_tokens_to_urls.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/helpers/resolve_media_tokens_to_urls.rs new file mode 100644 index 0000000000..8ee7d7f175 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/helpers/resolve_media_tokens_to_urls.rs @@ -0,0 +1,64 @@ +use std::collections::HashMap; + +use artcraft_router::api::audio_list_ref::AudioListRef; +use artcraft_router::api::image_list_ref::ImageListRef; +use artcraft_router::api::image_ref::ImageRef; +use artcraft_router::api::video_list_ref::VideoListRef; +use artcraft_router::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; +use tokens::tokens::media_files::MediaFileToken; + +/// For providers that take URLs directly (GmiCloud, Fal), swap +/// `ImageRef::MediaFileToken` → `ImageRef::Url` using the resolved map. +pub fn resolve_media_tokens_to_urls( + builder: &mut GenerateVideoRequestBuilder, + url_map: Option<&HashMap>, +) { + let map = match url_map { + Some(m) => m, + None => return, + }; + + // start_frame + if let Some(ImageRef::MediaFileToken(ref token)) = builder.start_frame { + if let Some(url) = map.get(token) { + builder.start_frame = Some(ImageRef::Url(url.clone())); + } + } + + // end_frame + if let Some(ImageRef::MediaFileToken(ref token)) = builder.end_frame { + if let Some(url) = map.get(token) { + builder.end_frame = Some(ImageRef::Url(url.clone())); + } + } + + // reference_images + if let Some(ImageListRef::MediaFileTokens(ref tokens)) = builder.reference_images { + let urls: Vec = tokens.iter() + .filter_map(|t| map.get(t).cloned()) + .collect(); + if !urls.is_empty() { + builder.reference_images = Some(ImageListRef::Urls(urls)); + } + } + + // reference_videos + if let Some(VideoListRef::MediaFileTokens(ref tokens)) = builder.reference_videos { + let urls: Vec = tokens.iter() + .filter_map(|t| map.get(t).cloned()) + .collect(); + if !urls.is_empty() { + builder.reference_videos = Some(VideoListRef::Urls(urls)); + } + } + + // reference_audio + if let Some(AudioListRef::MediaFileTokens(ref tokens)) = builder.reference_audio { + let urls: Vec = tokens.iter() + .filter_map(|t| map.get(t).cloned()) + .collect(); + if !urls.is_empty() { + builder.reference_audio = Some(AudioListRef::Urls(urls)); + } + } +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/insert_db_job/insert_fal_job.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/insert_db_job/insert_fal_job.rs new file mode 100644 index 0000000000..b71ca9b184 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/insert_db_job/insert_fal_job.rs @@ -0,0 +1,57 @@ +use log::warn; + +use enums::common::visibility::Visibility; +use mysql_queries::queries::generic_inference::api_providers::fal::insert_generic_inference_job_for_fal_queue::FalCategory; +use mysql_queries::queries::generic_inference::api_providers::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{ + insert_generic_inference_job_for_fal_queue_with_apriori_job_token, + InsertGenericInferenceForFalWithAprioriJobTokenArgs, +}; +use tokens::tokens::generic_inference_jobs::InferenceJobToken; + +use crate::http_server::common_responses::common_web_error::CommonWebError; + +use super::shared_job_args::SharedJobArgs; + +pub struct InsertFalJobArgs<'a, 'tx> { + pub external_job_id: &'a str, + pub shared: SharedJobArgs<'a, 'tx>, +} + +pub async fn insert_fal_job(args: InsertFalJobArgs<'_, '_>) -> Result { + let InsertFalJobArgs { + external_job_id, + shared, + } = args; + + let db_result = insert_generic_inference_job_for_fal_queue_with_apriori_job_token( + InsertGenericInferenceForFalWithAprioriJobTokenArgs { + apriori_job_token: shared.apriori_job_token, + uuid_idempotency_token: shared.idempotency_token, + maybe_external_third_party_id: external_job_id, + fal_category: FalCategory::VideoGeneration, + maybe_model_type: shared.maybe_model_type, + maybe_prompt_token: shared.maybe_prompt_token, + maybe_creator_user_token: Some(shared.user_token), + maybe_avt_token: shared.maybe_avt_token, + creator_ip_address: shared.ip_address, + creator_set_visibility: Visibility::Public, + maybe_platform_type: shared.maybe_platform_type, + maybe_cost_estimates: shared.maybe_cost_estimates, + maybe_debug_log_event_token: shared.maybe_debug_log_event_token, + mysql_executor: &mut **shared.transaction, + maybe_inference_args: None, + starting_job_status_override: None, + maybe_frontend_failure_category: None, + maybe_failure_reason: None, + phantom: Default::default(), + } + ).await; + + match db_result { + Ok(token) => Ok(token), + Err(err) => { + warn!("Error inserting fal inference job: {:?}", err); + Err(CommonWebError::from_error(err)) + } + } +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/insert_db_job/insert_gmicloud_job.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/insert_db_job/insert_gmicloud_job.rs new file mode 100644 index 0000000000..776318f419 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/insert_db_job/insert_gmicloud_job.rs @@ -0,0 +1,48 @@ +use log::warn; + +use mysql_queries::queries::generic_inference::api_providers::gmicloud::insert_generic_inference_job_for_gmicloud::{ + insert_generic_inference_job_for_gmicloud, InsertGmiCloudInferenceJobArgs, +}; +use tokens::tokens::generic_inference_jobs::InferenceJobToken; + +use crate::http_server::common_responses::common_web_error::CommonWebError; + +use super::shared_job_args::SharedJobArgs; + +pub struct InsertGmiCloudJobArgs<'a, 'tx> { + pub external_request_id: &'a str, + pub shared: SharedJobArgs<'a, 'tx>, +} + +pub async fn insert_gmicloud_job(args: InsertGmiCloudJobArgs<'_, '_>) -> Result { + let InsertGmiCloudJobArgs { + external_request_id, + shared, + } = args; + + let db_result = insert_generic_inference_job_for_gmicloud( + InsertGmiCloudInferenceJobArgs { + apriori_job_token: shared.apriori_job_token, + uuid_idempotency_token: shared.idempotency_token, + external_request_id, + maybe_model_type: shared.maybe_model_type, + maybe_prompt_token: shared.maybe_prompt_token, + maybe_creator_user_token: Some(shared.user_token), + maybe_avt_token: shared.maybe_avt_token, + creator_ip_address: shared.ip_address, + maybe_platform_type: shared.maybe_platform_type, + maybe_cost_estimates: shared.maybe_cost_estimates, + maybe_debug_log_event_token: shared.maybe_debug_log_event_token, + mysql_executor: &mut **shared.transaction, + phantom: Default::default(), + } + ).await; + + match db_result { + Ok(token) => Ok(token), + Err(err) => { + warn!("Error inserting GmiCloud inference job: {:?}", err); + Err(CommonWebError::from_error(err)) + } + } +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/insert_db_job/insert_grok_api_job.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/insert_db_job/insert_grok_api_job.rs new file mode 100644 index 0000000000..76e4b70d00 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/insert_db_job/insert_grok_api_job.rs @@ -0,0 +1,48 @@ +use log::warn; + +use mysql_queries::queries::generic_inference::api_providers::grok_api::insert_generic_inference_job_for_grok_api::{ + insert_generic_inference_job_for_grok_api, InsertGrokApiInferenceJobArgs, +}; +use tokens::tokens::generic_inference_jobs::InferenceJobToken; + +use crate::http_server::common_responses::common_web_error::CommonWebError; + +use super::shared_job_args::SharedJobArgs; + +pub struct InsertGrokApiJobArgs<'a, 'tx> { + pub external_request_id: &'a str, + pub shared: SharedJobArgs<'a, 'tx>, +} + +pub async fn insert_grok_api_job(args: InsertGrokApiJobArgs<'_, '_>) -> Result { + let InsertGrokApiJobArgs { + external_request_id, + shared, + } = args; + + let db_result = insert_generic_inference_job_for_grok_api( + InsertGrokApiInferenceJobArgs { + apriori_job_token: shared.apriori_job_token, + uuid_idempotency_token: shared.idempotency_token, + external_request_id, + maybe_model_type: shared.maybe_model_type, + maybe_prompt_token: shared.maybe_prompt_token, + maybe_creator_user_token: Some(shared.user_token), + maybe_avt_token: shared.maybe_avt_token, + creator_ip_address: shared.ip_address, + maybe_platform_type: shared.maybe_platform_type, + maybe_cost_estimates: shared.maybe_cost_estimates, + maybe_debug_log_event_token: shared.maybe_debug_log_event_token, + mysql_executor: &mut **shared.transaction, + phantom: Default::default(), + } + ).await; + + match db_result { + Ok(token) => Ok(token), + Err(err) => { + warn!("Error inserting Grok (xAI) API inference job: {:?}", err); + Err(CommonWebError::from_error(err)) + } + } +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/insert_db_job/insert_seedance2pro_jobs.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/insert_db_job/insert_seedance2pro_jobs.rs new file mode 100644 index 0000000000..5da1d5e915 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/insert_db_job/insert_seedance2pro_jobs.rs @@ -0,0 +1,95 @@ +use log::{error, warn}; + +use crate::http_server::common_responses::common_web_error::CommonWebError; +use enums::common::visibility::Visibility; +use mysql_queries::queries::generic_inference::api_providers::seedance2pro::insert_generic_inference_job_for_seedance2pro_queue_with_apriori_job_token::{insert_generic_inference_job_for_seedance2pro_queue_with_apriori_job_token, InsertGenericInferenceForSeedance2ProWithAprioriJobTokenArgs, KinoviVersion}; +use tokens::tokens::generic_inference_jobs::InferenceJobToken; +use tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken; +use uuid_utils::uuid::generate_random_uuid; + +use super::shared_job_args::SharedJobArgs; + +pub struct InsertSeedance2proJobsArgs<'a, 'tx> { + pub primary_order_id: &'a str, + pub maybe_additional_order_ids: Option<&'a [String]>, + pub maybe_wallet_ledger_entry_token: Option<&'a WalletLedgerEntryToken>, + pub kinovi_version: KinoviVersion, + pub shared: SharedJobArgs<'a, 'tx>, +} + +pub struct InsertSeedance2proJobsResult { + pub primary_job_token: InferenceJobToken, + pub all_job_tokens: Vec, +} + +pub async fn insert_seedance2pro_jobs(args: InsertSeedance2proJobsArgs<'_, '_>) -> Result { + let InsertSeedance2proJobsArgs { + primary_order_id, + maybe_additional_order_ids, + maybe_wallet_ledger_entry_token, + kinovi_version, + mut shared, + } = args; + + // Build a deduplicated list with primary_order_id first. + let mut all_order_ids = vec![primary_order_id.to_string()]; + + if let Some(additional) = maybe_additional_order_ids { + for id in additional { + if id != primary_order_id { + all_order_ids.push(id.clone()); + } + } + } + + let mut all_job_tokens: Vec = Vec::with_capacity(all_order_ids.len()); + + for (i, order_id) in all_order_ids.iter().enumerate() { + let job_token = if i == 0 { shared.apriori_job_token.clone() } else { InferenceJobToken::generate() }; + + // TODO: These strings are too long for the database: + //let idempotency_str = if i == 0 { shared.idempotency_token.to_string() } else { format!("{}-batch-{}", shared.idempotency_token, i) }; + + let idempotency_str = generate_random_uuid(); + + let db_result = insert_generic_inference_job_for_seedance2pro_queue_with_apriori_job_token( + InsertGenericInferenceForSeedance2ProWithAprioriJobTokenArgs { + kinovi_version, + apriori_job_token: &job_token, + uuid_idempotency_token: &idempotency_str, + maybe_external_third_party_id: order_id, + maybe_model_type: shared.maybe_model_type, + maybe_prompt_token: shared.maybe_prompt_token, + maybe_wallet_ledger_entry_token, + maybe_creator_user_token: Some(shared.user_token), + maybe_avt_token: shared.maybe_avt_token, + creator_ip_address: shared.ip_address, + creator_set_visibility: Visibility::Public, + maybe_platform_type: shared.maybe_platform_type, + maybe_cost_estimates: shared.maybe_cost_estimates, + maybe_debug_log_event_token: shared.maybe_debug_log_event_token, + mysql_executor: &mut **shared.transaction, + maybe_inference_args: None, + phantom: Default::default(), + } + ).await; + + match db_result { + Ok(token) => all_job_tokens.push(token), + Err(err) => { + warn!("Error inserting seedance2pro inference job (order_id={}): {:?}", order_id, err); + if i == 0 { return Err(CommonWebError::from_error(err)); } + } + } + } + + let primary_job_token = all_job_tokens.first().cloned().ok_or_else(|| { + error!("No inference job token was created"); + CommonWebError::server_error_with_message("No inference job token was created") + })?; + + Ok(InsertSeedance2proJobsResult { + primary_job_token, + all_job_tokens, + }) +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/insert_db_job/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/insert_db_job/mod.rs new file mode 100644 index 0000000000..4591de2a46 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/insert_db_job/mod.rs @@ -0,0 +1,5 @@ +pub mod insert_fal_job; +pub mod insert_gmicloud_job; +pub mod insert_grok_api_job; +pub mod insert_seedance2pro_jobs; +pub mod shared_job_args; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/insert_db_job/shared_job_args.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/insert_db_job/shared_job_args.rs new file mode 100644 index 0000000000..69215d9ce0 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/insert_db_job/shared_job_args.rs @@ -0,0 +1,22 @@ +use enums::common::generation::common_model_type::CommonModelType; +use enums::common::platform_type::PlatformType; +use mysql_queries::queries::generic_inference::common::job_cost_estimates::JobCostEstimates; +use tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken; +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 SharedJobArgs<'a, 'tx> { + pub apriori_job_token: &'a InferenceJobToken, + pub idempotency_token: &'a str, + pub user_token: &'a UserToken, + pub maybe_avt_token: Option<&'a AnonymousVisitorTrackingToken>, + pub maybe_prompt_token: Option<&'a PromptToken>, + pub maybe_debug_log_event_token: Option<&'a DebugLogEventToken>, + pub maybe_model_type: Option, + pub maybe_platform_type: Option, + pub maybe_cost_estimates: Option, + pub ip_address: &'a str, + pub transaction: &'a mut sqlx::Transaction<'tx, sqlx::MySql>, +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/kinovi_account.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/kinovi_account.rs new file mode 100644 index 0000000000..17277f6934 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/kinovi_account.rs @@ -0,0 +1,7 @@ + +#[derive(Debug, Clone, Copy)] +pub enum KinoviAccount { + Volcengine, + BytePlus, + BytePlusUltra, +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/mod.rs new file mode 100644 index 0000000000..95b669cdcd --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/mod.rs @@ -0,0 +1,5 @@ +pub mod omni_api_video_generate_handler; +pub mod helpers; +pub mod pipeline_v2; +pub mod insert_db_job; +pub mod kinovi_account; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/omni_api_video_generate_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/omni_api_video_generate_handler.rs new file mode 100644 index 0000000000..62c26f18b2 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/omni_api_video_generate_handler.rs @@ -0,0 +1,477 @@ +use std::collections::HashMap; +use std::sync::Arc; + +use actix_web::web::Json; +use actix_web::{web, HttpRequest}; +use log::{error, info, warn}; +use sqlx::Acquire; + +use artcraft_api_defs::omni_gen::cost_and_generate_requests::omni_gen_video_cost_and_generate_request::OmniGenVideoCostAndGenerateRequest; +use artcraft_api_defs::omni_gen::generate_response::omni_gen_video_generate_response::OmniGenVideoGenerateResponse; +use artcraft_router::generate::generate_video::generate_video_response::GenerateVideoResponse; +use enums::by_table::debug_logs::debug_log_type::DebugLogType; +use enums::by_table::prompt_context_items::prompt_context_semantic_type::PromptContextSemanticType; +use enums::by_table::prompts::prompt_type::PromptType; +use enums::common::generation::common_generation_mode::CommonGenerationMode; +use enums::common::generation::common_model_type::CommonModelType; +use enums::common::generation::common_video_model::CommonVideoModel; +use enums::common::generation_provider::GenerationProvider; +use http_server_common::request::get_request_ip::get_request_ip; +use mysql_queries::queries::debug_logs::insert_debug_log::{insert_debug_log, InsertDebugLogArgs}; +use mysql_queries::queries::generic_inference::api_providers::seedance2pro::insert_generic_inference_job_for_seedance2pro_queue_with_apriori_job_token::KinoviVersion; +use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token; +use mysql_queries::queries::prompt_context_items::insert_batch_prompt_context_items::{ + insert_batch_prompt_context_items, InsertBatchArgs, PromptContextItem, +}; +use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPromptArgs}; +use tokens::tokens::characters::CharacterToken; +use tokens::tokens::generic_inference_jobs::InferenceJobToken; +use tokens::tokens::media_files::MediaFileToken; +use tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken; + +use crate::http_server::common_responses::common_web_error::CommonWebError; +use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test; +use crate::http_server::endpoints::omni_api::generate::video::helpers::hydrate_router_request::hydrate_to_router_request; +use crate::http_server::endpoints::omni_api::generate::video::helpers::resolve_kinovi_character_ids::resolve_kinovi_character_ids; +use crate::http_server::endpoints::omni_api::generate::video::insert_db_job::insert_fal_job::{insert_fal_job, InsertFalJobArgs}; +use crate::http_server::endpoints::omni_api::generate::video::insert_db_job::insert_gmicloud_job::{insert_gmicloud_job, InsertGmiCloudJobArgs}; +use crate::http_server::endpoints::omni_api::generate::video::insert_db_job::insert_grok_api_job::{insert_grok_api_job, InsertGrokApiJobArgs}; +use crate::http_server::endpoints::omni_api::generate::video::insert_db_job::insert_seedance2pro_jobs::{insert_seedance2pro_jobs, InsertSeedance2proJobsArgs}; +use crate::http_server::endpoints::omni_api::generate::video::insert_db_job::shared_job_args::SharedJobArgs; +use crate::http_server::endpoints::omni_api::generate::video::kinovi_account::KinoviAccount; +use crate::http_server::endpoints::omni_api::generate::video::pipeline_v2::run_pipeline_v2::{run_pipeline_v2, RunPipelineV2Args}; +use crate::http_server::endpoints::omni_api::shared_utils::video::validate_video_request::validate_video_request; +use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; +use crate::http_server::user_lookup::api_keys::require_api_key_user::require_api_key_user; +use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; +use crate::http_server::web_utils::get_request_platform_type::get_request_platform_type; +use crate::state::server_state::ServerState; +use crate::util::lookup::lookup_media_files_as_cdn_url_list_and_map::lookup_media_files_as_cdn_url_list_and_map; + +/// Generate a video using the omni-api unified endpoint (API-key authenticated). +#[utoipa::path( + post, + tag = "Omni API", + path = "/v1/omni_api/generate/video", + request_body = OmniGenVideoCostAndGenerateRequest, + responses( + (status = 200, description = "Success", body = OmniGenVideoGenerateResponse), + (status = 400, description = "Bad input"), + (status = 401, description = "Unauthorized"), + (status = 402, description = "Payment required"), + (status = 500, description = "Server error"), + ), +)] +pub async fn omni_api_video_generate_handler( + http_request: HttpRequest, + request: Json, + server_state: web::Data>, +) -> Result, CommonWebError> { + + info!("request: {:?}", request); + + // Reject doomed combos (e.g. grok_imagine_video_1p5 without an image) + // before any billable or DB-mutating work — see helper for the rules. + validate_video_request(&request)?; + + payments_error_test(&request.prompt.as_deref().unwrap_or(""))?; + + let debug_log_event_token = DebugLogEventToken::generate(); + + let maybe_prompt_model_type: Option = request.model + .as_ref() + .map(|m| m.to_common_model_type()); + + // ==================== API KEY USER ==================== // + + let mut mysql_connection = server_state.mysql_pool.acquire().await?; + + // API-key authentication (Authorization header) instead of a session cookie. Never cached. + let api_session = require_api_key_user(&http_request, &mut *mysql_connection).await?; + + let user_token = &api_session.user_token; + + let maybe_avt_token = server_state + .avt_cookie_manager + .get_avt_token_from_request(&http_request); + + // ==================== MODEL ACCESS CHECK ==================== // + + // API-key users have no per-user feature flags today. + let user_feature_flags = UserSessionFeatureFlags::new(None); + + // ==================== IDEMPOTENCY ==================== // + + let idempotency_token = request.idempotency_token.as_deref() + .unwrap_or("") + .to_string(); + + if let Err(reason) = validate_idempotency_token_format(&idempotency_token) { + return Err(CommonWebError::BadInputWithSimpleMessage(reason)); + } + + insert_idempotency_token(&idempotency_token, &mut *mysql_connection) + .await + .map_err(|err| { + error!("Error inserting idempotency token: {:?}", err); + CommonWebError::BadInputWithSimpleMessage("repeated idempotency token".to_string()) + })?; + + // ==================== RESOLVE MEDIA TOKENS ==================== // + + let mut all_tokens: Vec = Vec::new(); + + if let Some(token) = &request.start_frame_image_media_token { + all_tokens.push(token.clone()); + } + if let Some(token) = &request.end_frame_image_media_token { + all_tokens.push(token.clone()); + } + if let Some(tokens) = &request.reference_image_media_tokens { + all_tokens.extend(tokens.iter().cloned()); + } + if let Some(tokens) = &request.reference_video_media_tokens { + all_tokens.extend(tokens.iter().cloned()); + } + if let Some(tokens) = &request.reference_audio_media_tokens { + all_tokens.extend(tokens.iter().cloned()); + } + + let media_file_to_url_map: Option> = if all_tokens.is_empty() { + None + } else { + info!("Resolving {} media file tokens to CDN URLs", all_tokens.len()); + let resolved = lookup_media_files_as_cdn_url_list_and_map( + &http_request, + &mut mysql_connection, + server_state.server_environment, + &all_tokens, + ).await?; + Some(resolved.token_to_url_map) + }; + + // ==================== RESOLVE CHARACTERS ==================== // + + let kinovi_character_id_map: Option> = + resolve_kinovi_character_ids( + request.reference_character_tokens.as_deref(), + &mut mysql_connection, + ).await?; + + // ==================== HYDRATE ROUTER REQUEST ==================== // + + let router_builder = hydrate_to_router_request(&request)?; + + // ==================== PIPELINE DISPATCH ==================== // + + let kinovi_account = match request.model { + // BytePlus Ultra + Some(CommonVideoModel::Seedance2p0BytePlusUltra) => KinoviAccount::BytePlusUltra, + Some(CommonVideoModel::Seedance2p0BytePlusUltraFast) => KinoviAccount::BytePlusUltra, + // BytePlus + Some(CommonVideoModel::Seedance2p0BytePlus) => KinoviAccount::BytePlus, + Some(CommonVideoModel::Seedance2p0BytePlusFast) => KinoviAccount::BytePlus, + Some(CommonVideoModel::PreviewModel) => KinoviAccount::BytePlus, + Some(CommonVideoModel::PreviewModelFast) => KinoviAccount::BytePlus, + // Everything else goes through Volcengine + _ => KinoviAccount::Volcengine, + }; + + // ==================== DEBUG LOG: HTTP REQUEST ==================== // + + if let Err(err) = insert_debug_log(InsertDebugLogArgs { + apriori_debug_log_event_token: Some(&debug_log_event_token), + maybe_creator_user_token: Some(user_token), + debug_log_type: DebugLogType::HttpRequest, + message: &serde_json::to_string(&*request).unwrap_or_default(), + mysql_executor: &mut *mysql_connection, + phantom: Default::default(), + }).await { + warn!("Failed to insert HTTP request debug log: {:?}", err); + } + + // NB: Release the pooled DB connection before the (slow, external) generation call so we don't + // hold a pool slot idle while waiting on the provider — that's what starves the pool and causes + // PoolTimedOut on unrelated endpoints. We re-acquire below to write the result. + drop(mysql_connection); + + let pipeline_result = run_pipeline_v2(RunPipelineV2Args { + router_builder: &router_builder, + server_state: &server_state, + user_token, + media_file_to_url_map: &media_file_to_url_map, + kinovi_character_id_map: &kinovi_character_id_map, + kinovi_account, + }).await?; + + let mut mysql_connection = server_state.mysql_pool.acquire().await?; + + // ==================== DEBUG LOG: FAL REQUEST ==================== // + + if let GenerateVideoResponse::Fal(ref fal_payload) = pipeline_result.response { + if let Some(ref outbound_request) = fal_payload.maybe_outbound_request { + if let Err(err) = insert_debug_log(InsertDebugLogArgs { + apriori_debug_log_event_token: Some(&debug_log_event_token), + maybe_creator_user_token: Some(user_token), + debug_log_type: DebugLogType::FalRequest, + message: &format!("{:#?}", outbound_request), + mysql_executor: &mut *mysql_connection, + phantom: Default::default(), + }).await { + warn!("Failed to insert Fal request debug log: {:?}", err); + } + } + } + + // ==================== DEBUG LOG: GROK API REQUEST ==================== // + + if let GenerateVideoResponse::Grok(ref grok_payload) = pipeline_result.response { + if let Some(ref outbound_request) = grok_payload.maybe_outbound_request { + if let Err(err) = insert_debug_log(InsertDebugLogArgs { + apriori_debug_log_event_token: Some(&debug_log_event_token), + maybe_creator_user_token: Some(user_token), + debug_log_type: DebugLogType::GrokApiRequest, + message: &format!("{:#?}", outbound_request), + mysql_executor: &mut *mysql_connection, + phantom: Default::default(), + }).await { + warn!("Failed to insert Grok API request debug log: {:?}", err); + } + } + } + + // ==================== WRITE RESULT ==================== // + + let ip_address = get_request_ip(&http_request); + let maybe_platform_type = get_request_platform_type(&http_request); + + let mut transaction = mysql_connection.begin().await.map_err(|err| { + error!("Error starting MySQL transaction: {:?}", err); + CommonWebError::from_error(err) + })?; + + // -- Prompt -- + + let prompt_token = match insert_prompt(InsertPromptArgs { + maybe_apriori_prompt_token: None, + prompt_type: PromptType::ArtcraftApp, + maybe_creator_user_token: Some(user_token), + maybe_model_type: maybe_prompt_model_type, + maybe_generation_provider: Some(GenerationProvider::Artcraft), + maybe_positive_prompt: request.prompt.as_deref(), + maybe_negative_prompt: request.negative_prompt.as_deref(), + maybe_other_args: None, + maybe_generation_mode: Some(determine_generation_mode(&request)), + maybe_aspect_ratio: request.aspect_ratio, + maybe_resolution: request.resolution, + maybe_batch_count: request.video_batch_count.map(|c| c as u8), + maybe_generate_audio: request.generate_audio, + maybe_duration_seconds: request.duration_seconds.map(|d| d as u32), + creator_ip_address: &ip_address, + mysql_executor: &mut *transaction, + phantom: Default::default(), + }).await { + Ok(token) => Some(token), + Err(err) => { + warn!("Error inserting prompt: {:?}", err); + None + } + }; + + // -- Prompt context items -- + + if let Some(token) = prompt_token.as_ref() { + let mut context_items = Vec::new(); + + if let Some(media_token) = &request.start_frame_image_media_token { + context_items.push(PromptContextItem { + media_token: media_token.clone(), + context_semantic_type: PromptContextSemanticType::VidStartFrame, + }); + } + if let Some(media_token) = &request.end_frame_image_media_token { + context_items.push(PromptContextItem { + media_token: media_token.clone(), + context_semantic_type: PromptContextSemanticType::VidEndFrame, + }); + } + if let Some(ref_tokens) = &request.reference_image_media_tokens { + for media_token in ref_tokens { + context_items.push(PromptContextItem { + media_token: media_token.clone(), + context_semantic_type: PromptContextSemanticType::Imgref, + }); + } + } + if let Some(ref_tokens) = &request.reference_video_media_tokens { + for media_token in ref_tokens { + context_items.push(PromptContextItem { + media_token: media_token.clone(), + context_semantic_type: PromptContextSemanticType::VidRef, + }); + } + } + + if !context_items.is_empty() { + if let Err(err) = insert_batch_prompt_context_items(InsertBatchArgs { + prompt_token: token.clone(), + items: context_items, + transaction: &mut transaction, + }).await { + warn!("Error inserting batch prompt context items: {:?}", err); + } + } + } + + // -- Inference job -- + + let (primary_job_token, all_job_tokens) = match &pipeline_result.response { + GenerateVideoResponse::Seedance2Pro(payload) => { + info!("Inserting seedance2pro job(s) with token: {:?}", pipeline_result.billing.apriori_job_token); + + let kinovi_version = match kinovi_account { + KinoviAccount::Volcengine => KinoviVersion::Volcengine, + KinoviAccount::BytePlus => KinoviVersion::BytePlus, + KinoviAccount::BytePlusUltra => KinoviVersion::BytePlusUltra, + }; + + let result = insert_seedance2pro_jobs(InsertSeedance2proJobsArgs { + primary_order_id: &payload.order_id, + maybe_additional_order_ids: payload.maybe_order_ids.as_deref(), + maybe_wallet_ledger_entry_token: pipeline_result.billing.maybe_wallet_ledger_entry_token.as_ref(), + kinovi_version, + shared: SharedJobArgs { + apriori_job_token: &pipeline_result.billing.apriori_job_token, + idempotency_token: &idempotency_token, + user_token, + maybe_avt_token: maybe_avt_token.as_ref(), + maybe_model_type: request.model.map(|v| v.to_common_model_type()), + maybe_prompt_token: prompt_token.as_ref(), + maybe_debug_log_event_token: Some(&debug_log_event_token), + maybe_platform_type, + maybe_cost_estimates: Some(pipeline_result.cost_estimates), + ip_address: &ip_address, + transaction: &mut transaction, + }, + }).await?; + (result.primary_job_token, result.all_job_tokens) + } + GenerateVideoResponse::Fal(payload) => { + let external_id = payload.request_id.as_deref().ok_or_else(|| { + error!("Fal generation response missing request_id"); + CommonWebError::server_error_with_message("Fal generation response missing request_id") + })?; + info!("Inserting fal job with token: {:?}", pipeline_result.billing.apriori_job_token); + let token = insert_fal_job(InsertFalJobArgs { + external_job_id: external_id, + shared: SharedJobArgs { + apriori_job_token: &pipeline_result.billing.apriori_job_token, + idempotency_token: &idempotency_token, + user_token, + maybe_avt_token: maybe_avt_token.as_ref(), + maybe_model_type: request.model.map(|v| v.to_common_model_type()), + maybe_prompt_token: prompt_token.as_ref(), + maybe_debug_log_event_token: Some(&debug_log_event_token), + maybe_platform_type, + maybe_cost_estimates: Some(pipeline_result.cost_estimates), + ip_address: &ip_address, + transaction: &mut transaction, + }, + }).await?; + + ( + token.clone(), + vec![token], + ) + } + GenerateVideoResponse::Artcraft(payload) => { + ( + payload.inference_job_token.clone(), + vec![payload.inference_job_token.clone()], + ) + } + GenerateVideoResponse::GmiCloud(payload) => { + info!("Inserting GmiCloud job with token: {:?}", pipeline_result.billing.apriori_job_token); + let token = insert_gmicloud_job(InsertGmiCloudJobArgs { + external_request_id: &payload.request_id, + shared: SharedJobArgs { + apriori_job_token: &pipeline_result.billing.apriori_job_token, + idempotency_token: &idempotency_token, + user_token, + maybe_avt_token: maybe_avt_token.as_ref(), + maybe_model_type: request.model.map(|v| v.to_common_model_type()), + maybe_prompt_token: prompt_token.as_ref(), + maybe_debug_log_event_token: Some(&debug_log_event_token), + maybe_platform_type, + maybe_cost_estimates: Some(pipeline_result.cost_estimates), + ip_address: &ip_address, + transaction: &mut transaction, + }, + }).await?; + + ( + token.clone(), + vec![token], + ) + } + GenerateVideoResponse::Grok(payload) => { + info!("Inserting Grok (xAI) API job with token: {:?}", pipeline_result.billing.apriori_job_token); + let token = insert_grok_api_job(InsertGrokApiJobArgs { + external_request_id: &payload.request_id, + shared: SharedJobArgs { + apriori_job_token: &pipeline_result.billing.apriori_job_token, + idempotency_token: &idempotency_token, + user_token, + maybe_avt_token: maybe_avt_token.as_ref(), + maybe_model_type: request.model.map(|v| v.to_common_model_type()), + maybe_prompt_token: prompt_token.as_ref(), + maybe_debug_log_event_token: Some(&debug_log_event_token), + maybe_platform_type, + maybe_cost_estimates: Some(pipeline_result.cost_estimates), + ip_address: &ip_address, + transaction: &mut transaction, + }, + }).await?; + + ( + token.clone(), + vec![token], + ) + } + other => { + error!("Unexpected generation response variant: {:?}", other); + return Err(CommonWebError::server_error_with_message("Unexpected generation response")); + } + }; + + transaction.commit().await.map_err(|err| { + error!("Error committing transaction: {:?}", err); + CommonWebError::from_error(err) + })?; + + Ok(Json(OmniGenVideoGenerateResponse { + success: true, + inference_job_token: primary_job_token, + all_job_tokens, + })) +} + +fn determine_generation_mode(request: &OmniGenVideoCostAndGenerateRequest) -> CommonGenerationMode { + let has_keyframe = request.start_frame_image_media_token.is_some() + || request.end_frame_image_media_token.is_some(); + + if has_keyframe { + return CommonGenerationMode::Keyframe; + } + + let has_reference = request.reference_image_media_tokens.as_ref().is_some_and(|t| !t.is_empty()) + || request.reference_video_media_tokens.as_ref().is_some_and(|t| !t.is_empty()) + || request.reference_audio_media_tokens.as_ref().is_some_and(|t| !t.is_empty()) + || request.reference_character_tokens.as_ref().is_some_and(|t| !t.is_empty()); + + if has_reference { + return CommonGenerationMode::Reference; + } + + CommonGenerationMode::Text +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/pipeline_v2/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/pipeline_v2/mod.rs new file mode 100644 index 0000000000..f207b21293 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/pipeline_v2/mod.rs @@ -0,0 +1 @@ +pub mod run_pipeline_v2; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/pipeline_v2/run_pipeline_v2.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/pipeline_v2/run_pipeline_v2.rs new file mode 100644 index 0000000000..19288349dd --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/pipeline_v2/run_pipeline_v2.rs @@ -0,0 +1,227 @@ +use std::convert::TryFrom; + +use std::collections::HashMap; + +use log::{error, info, warn}; +use artcraft_router::api::router_video_model::RouterVideoModel; +use artcraft_router::api::router_provider::RouterProvider; +use artcraft_router::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; +use artcraft_router::generate::generate_video::generate_video_response::GenerateVideoResponse; +use artcraft_router::generate::generate_video::video_generation_draft_context::VideoGenerationDraftContext; +use artcraft_router::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest; +use tokens::tokens::characters::CharacterToken; +use tokens::tokens::media_files::MediaFileToken; +use tokens::tokens::users::UserToken; + +use crate::http_server::common_responses::common_web_error::CommonWebError; +use crate::http_server::endpoint_helpers::refund_wallet_after_api_failure::refund_wallet_after_api_failure; +use crate::http_server::endpoints::omni_api::generate::video::helpers::bill_wallet::bill_wallet; +use crate::http_server::endpoints::omni_api::generate::video::helpers::build_router_client::build_router_client; +use crate::http_server::endpoints::omni_api::generate::video::helpers::pipeline_result::PipelineResult; +use crate::http_server::endpoints::omni_api::generate::video::helpers::resolve_media_tokens_to_urls::resolve_media_tokens_to_urls; +use mysql_queries::queries::generic_inference::common::job_cost_estimates::JobCostEstimates; +use crate::http_server::endpoints::omni_api::generate::video::kinovi_account::KinoviAccount; +use crate::http_server::endpoints::omni_api::shared_utils::map_seedance2pro_router_error::map_router_error_to_web_error; +use crate::state::server_state::ServerState; + +pub struct RunPipelineV2Args<'a> { + pub router_builder: &'a GenerateVideoRequestBuilder, + pub server_state: &'a ServerState, + pub user_token: &'a UserToken, + pub media_file_to_url_map: &'a Option>, + pub kinovi_character_id_map: &'a Option>, + pub kinovi_account: KinoviAccount, +} + +// NB: This pipeline does an external generation call (`upload_and_generate`) that can take many +// seconds. It deliberately does NOT hold a pooled DB connection across that call — it acquires +// short-lived connections only for the billing and (on failure) refund writes. Holding a pooled +// connection across the external call is what starves the pool and causes `PoolTimedOut`. +pub async fn run_pipeline_v2(args: RunPipelineV2Args<'_>) -> Result { + let RunPipelineV2Args { + router_builder, + server_state, + user_token, + media_file_to_url_map, + kinovi_character_id_map, + kinovi_account, + } = args; + + let mut router_builder = router_builder.clone(); + + match router_builder.model { + RouterVideoModel::PreviewModel | + RouterVideoModel::Seedance2p0BytePlus | + RouterVideoModel::Seedance2p0BytePlusUltra => { + router_builder.model = RouterVideoModel::Seedance2p0; + }, + RouterVideoModel::PreviewModelFast | + RouterVideoModel::Seedance2p0BytePlusFast | + RouterVideoModel::Seedance2p0BytePlusUltraFast => { + router_builder.model = RouterVideoModel::Seedance2p0Fast; + }, + _ => {}, // Fall-through + } + + let provider = match router_builder.model { + RouterVideoModel::HappyHorse1p0 => RouterProvider::Seedance2Pro, + RouterVideoModel::Seedance2p0 => RouterProvider::Seedance2Pro, + RouterVideoModel::Seedance2p0Fast => RouterProvider::Seedance2Pro, + //RouterVideoModel::Seedance2p0Ultra => RouterProvider::GmiCloud, + //RouterVideoModel::Seedance2p0UltraFast => RouterProvider::GmiCloud, + RouterVideoModel::GrokImagineVideo => RouterProvider::GrokApi, + RouterVideoModel::GrokImagineVideo1p5 => RouterProvider::GrokApi, + _ => RouterProvider::Fal, + }; + + // 1. Build execution request + let mut exec_builder = router_builder.clone(); + exec_builder.provider = provider; + + // Fal, GmiCloud, and Grok (xAI) take image URLs directly, not media file tokens. + // Resolve tokens to URLs before building. + if matches!(provider, RouterProvider::Fal | RouterProvider::GmiCloud | RouterProvider::GrokApi) { + resolve_media_tokens_to_urls(&mut exec_builder, media_file_to_url_map.as_ref()); + } + + let draft_or_request = exec_builder.build2() + .map_err(|e| { + warn!("Failed to build2 for v2 pipeline: {}", e); + CommonWebError::from_error(e) + })?; + + // 2. Calculate cost. + // For Artcraft-billable models, swap provider to Artcraft so credits = cents. + // For GmiCloud, use the execution request's cost directly (no Artcraft equivalent). + let system_cost_estimate = { + let mut cost_builder = router_builder.clone(); + cost_builder.provider = RouterProvider::Artcraft; + + cost_builder.build2() + .map_err(|e| { + warn!("Failed to build2 cost estimate for v2: {}", e); + CommonWebError::from_error(e) + })? + .estimate_cost() + .map_err(|e| { + warn!("Failed to estimate cost for v2: {}", e); + CommonWebError::from_error(e) + })? + }; + + let cost = system_cost_estimate.cost_in_credits.unwrap_or(0); + + // Provider-side estimate (what the fulfilling provider charges us). The + // router defers to the underlying provider crates (seedance2pro_client, + // gmicloud_client, grok_api_client, the fal pricing modules, etc.) per + // request variant. Bookkeeping only — failures must not block generation. + let maybe_provider_cost_estimate = match draft_or_request.estimate_cost() { + Ok(estimate) => Some(estimate), + Err(err) => { + warn!("Failed to estimate provider cost for v2 video: {}", err); + None + } + }; + + let cost_estimates = JobCostEstimates { + maybe_external_third_party_cost_credits: maybe_provider_cost_estimate.as_ref() + .and_then(|e| e.cost_in_credits) + .and_then(|v| u32::try_from(v).ok()), + maybe_external_third_party_cost_usd_cents: maybe_provider_cost_estimate.as_ref() + .and_then(|e| e.cost_in_usd_cents) + .and_then(|v| u32::try_from(v).ok()), + maybe_system_cost_credits: system_cost_estimate.cost_in_credits + .and_then(|v| u32::try_from(v).ok()), + maybe_system_cost_usd_cents: system_cost_estimate.cost_in_usd_cents + .and_then(|v| u32::try_from(v).ok()), + }; + + info!("v2 estimated cost: {} credits (estimates: {:?})", cost, cost_estimates); + + // 3. Bill wallet (short-lived connection — released before the external call below). + let billing = { + let mut billing_connection = server_state.mysql_pool.acquire().await + .map_err(|err| { + error!("Failed to acquire MySQL connection for billing: {:?}", err); + CommonWebError::from_error(err) + })?; + bill_wallet(user_token, cost, &mut billing_connection).await? + }; + + // 4. Upload media (if draft) and generate video. + // The entire block is wrapped so Kinovi failures trigger a refund. + // NB: No pooled DB connection is held across this call. + let result = upload_and_generate( + draft_or_request, + server_state, + media_file_to_url_map.as_ref(), + kinovi_character_id_map.as_ref(), + kinovi_account, + ).await; + + // 5. On failure, refund wallet for Kinovi requests. + if let Err(ref err) = result { + if matches!(provider, RouterProvider::Seedance2Pro) { + if let Some(ledger_entry_token) = billing.maybe_wallet_ledger_entry_token.as_ref() { + warn!("Kinovi v2 generation failed, issuing refund for {}: {:?}", ledger_entry_token.as_str(), err); + + match server_state.mysql_pool.acquire().await { + Ok(mut refund_connection) => { + if let Err(refund_err) = refund_wallet_after_api_failure(ledger_entry_token, &mut refund_connection).await { + error!("Failed to refund wallet after Kinovi v2 failure: {:?}", refund_err); + } + } + Err(acquire_err) => { + error!("Failed to acquire MySQL connection to refund wallet after Kinovi v2 failure: {:?}", acquire_err); + } + } + } + } + } + + let response = result?; + + info!("v2 generation response: {:?}", response); + + Ok(PipelineResult { billing, response, cost_estimates }) +} + +/// Finalize the draft (uploading media if needed), then send the generation request. +/// +/// This is the block that gets refunded on failure for Kinovi providers. +async fn upload_and_generate( + draft_or_request: VideoGenerationDraftOrRequest, + server_state: &ServerState, + media_file_urls_by_token: Option<&HashMap>, + kinovi_character_ids: Option<&HashMap>, + kinovi_account: KinoviAccount, +) -> Result { + + let provider = draft_or_request.get_provider(); + let client = build_router_client(provider, server_state, kinovi_account)?; + + let video_request = match draft_or_request { + VideoGenerationDraftOrRequest::Request(request) => request, + VideoGenerationDraftOrRequest::Draft(draft) => { + let draft_context = VideoGenerationDraftContext { + client: Some(&client), + media_file_to_artcraft_url_map: media_file_urls_by_token, + character_token_to_kinovi_id_map: kinovi_character_ids, + }; + + draft.finalize(draft_context) + .await + .map_err(|err| { + warn!("Failed to finalize v2 draft: {:?}", err); + map_router_error_to_web_error(err) + })? + } + }; + + video_request.send_request(&client) + .await + .map_err(|err| { + warn!("v2 video generation failed: {:?}", err); + map_router_error_to_web_error(err) + }) +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/mod.rs new file mode 100644 index 0000000000..5cd6f81640 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/mod.rs @@ -0,0 +1,2 @@ +pub mod generate; +pub mod shared_utils; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/shared_utils/map_seedance2pro_router_error.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/shared_utils/map_seedance2pro_router_error.rs new file mode 100644 index 0000000000..6815fa3869 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/shared_utils/map_seedance2pro_router_error.rs @@ -0,0 +1,90 @@ +//! Maps artcraft router errors to web errors for the omni_gen pipelines. +//! +//! Known Kinovi (Seedance2Pro) bad requests (content violations, over-long +//! prompts, too many urls) become 400s with a user-facing message. Everything +//! else is unanticipated and stays a 500. + +use artcraft_router::errors::artcraft_router_error::ArtcraftRouterError; +use artcraft_router::errors::provider_error::ProviderError; +use seedance2pro_client::error::seedance2pro_bad_request_api_error::Seedance2ProBadRequestApiError; +use seedance2pro_client::error::seedance2pro_error::Seedance2ProError; + +use crate::http_server::common_responses::common_web_error::CommonWebError; + +/// Map an artcraft router error to a web error, unwrapping Kinovi bad requests. +pub fn map_router_error_to_web_error(error: ArtcraftRouterError) -> CommonWebError { + match error { + ArtcraftRouterError::Provider( + ProviderError::Seedance2Pro(Seedance2ProError::ApiBadRequest(bad_request)) + ) => { + CommonWebError::BadInputWithSimpleMessage(user_facing_bad_request_message(&bad_request)) + } + other => CommonWebError::from_error(other), + } +} + +fn user_facing_bad_request_message(error: &Seedance2ProBadRequestApiError) -> String { + match error { + Seedance2ProBadRequestApiError::VideoGenerationViolation { .. } => { + "The generation request was flagged as a content violation. \ + Please adjust your prompt or input media and try again.".to_string() + } + Seedance2ProBadRequestApiError::PromptIsTooLong { .. } => { + "The prompt is too long. Please shorten it below 10,000 characters and try again.".to_string() + } + Seedance2ProBadRequestApiError::TooManyUrls { .. } => { + "Too many input files were attached. \ + Please remove some and try again.".to_string() + } + } +} + +#[cfg(test)] +mod tests { + use actix_web::ResponseError; + + use super::*; + + #[test] + fn kinovi_bad_requests_become_400s_with_user_facing_messages() { + let cases = [ + ( + Seedance2ProBadRequestApiError::VideoGenerationViolation { raw_body: "{}".to_string() }, + "content violation", + ), + ( + Seedance2ProBadRequestApiError::PromptIsTooLong { raw_body: "{}".to_string() }, + "prompt is too long", + ), + ( + Seedance2ProBadRequestApiError::TooManyUrls { raw_body: "{}".to_string() }, + "Too many input files", + ), + ]; + + for (bad_request, expected_message_fragment) in cases { + let error = map_router_error_to_web_error( + ArtcraftRouterError::Provider( + ProviderError::Seedance2Pro( + Seedance2ProError::ApiBadRequest(bad_request)))); + + assert_eq!(error.status_code().as_u16(), 400); + match error { + CommonWebError::BadInputWithSimpleMessage(message) => { + assert!( + message.contains(expected_message_fragment), + "message {:?} should contain {:?}", message, expected_message_fragment); + } + other => panic!("Expected BadInputWithSimpleMessage, got: {:?}", other), + } + } + } + + #[test] + fn other_router_errors_stay_500s() { + let error = map_router_error_to_web_error( + ArtcraftRouterError::UnsupportedModel("some_model".to_string())); + + assert_eq!(error.status_code().as_u16(), 500); + } +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/shared_utils/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/shared_utils/mod.rs new file mode 100644 index 0000000000..cf024715fc --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/shared_utils/mod.rs @@ -0,0 +1,2 @@ +pub mod map_seedance2pro_router_error; +pub mod video; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/shared_utils/video/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/shared_utils/video/mod.rs new file mode 100644 index 0000000000..bfdbcc2afa --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/shared_utils/video/mod.rs @@ -0,0 +1,2 @@ +pub mod validate_video_request; +mod validate_when_image_required; \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/shared_utils/video/validate_video_request.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/shared_utils/video/validate_video_request.rs new file mode 100644 index 0000000000..e4ad72396e --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/shared_utils/video/validate_video_request.rs @@ -0,0 +1,11 @@ +use crate::http_server::common_responses::common_web_error::CommonWebError; +use crate::http_server::endpoints::omni_api::shared_utils::video::validate_when_image_required::validate_when_image_required; +use artcraft_api_defs::omni_gen::cost_and_generate_requests::omni_gen_video_cost_and_generate_request::OmniGenVideoCostAndGenerateRequest; + +/// Validate requests before they incur user costs or send API requests +pub fn validate_video_request( + request: &OmniGenVideoCostAndGenerateRequest, +) -> Result<(), CommonWebError> { + validate_when_image_required(request)?; + Ok(()) +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/shared_utils/video/validate_when_image_required.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/shared_utils/video/validate_when_image_required.rs new file mode 100644 index 0000000000..c94faa6e48 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/shared_utils/video/validate_when_image_required.rs @@ -0,0 +1,157 @@ +use artcraft_api_defs::omni_gen::cost_and_generate_requests::omni_gen_video_cost_and_generate_request::OmniGenVideoCostAndGenerateRequest; +use enums::common::generation::common_video_model::CommonVideoModel; + +use crate::http_server::common_responses::common_web_error::CommonWebError; + +/// xAI's `grok-imagine-video-1.5-preview` model rejects text-to-video at +/// the upstream API. Reject the request at the handler layer so callers see +/// a clean 400 with an actionable message and we don't charge their wallet +/// for an inference job we know will fail. +pub (super) fn validate_when_image_required( + request: &OmniGenVideoCostAndGenerateRequest, +) -> Result<(), CommonWebError> { + match request.model { + Some(CommonVideoModel::GrokImagineVideo1p5) => {}, // Fall-through + _ => return Ok(()), + } + + let requires_image = matches!(request.model, Some(CommonVideoModel::GrokImagineVideo1p5)); + + if !requires_image { + return Ok(()); + } + + let has_start_frame = request.start_frame_image_media_token.is_some(); + + let has_reference_images = request + .reference_image_media_tokens + .as_ref() + .is_some_and(|v| !v.is_empty()); + + if has_start_frame || has_reference_images { + return Ok(()); + } + + Err(CommonWebError::BadInputWithSimpleMessage( + "Image is required".to_string(), + )) +} + +#[cfg(test)] +mod tests { + use super::*; + use tokens::tokens::media_files::MediaFileToken; + + fn base_request() -> OmniGenVideoCostAndGenerateRequest { + OmniGenVideoCostAndGenerateRequest { + idempotency_token: Some("a1b2c3d4-e5f6-7890-abcd-ef1234567890".to_string()), + model: None, + prompt: Some("test".to_string()), + negative_prompt: None, + start_frame_image_media_token: None, + end_frame_image_media_token: None, + reference_image_media_tokens: None, + reference_video_media_tokens: None, + reference_audio_media_tokens: None, + reference_character_tokens: None, + resolution: None, + aspect_ratio: None, + quality: None, + duration_seconds: Some(5), + video_batch_count: Some(1), + generate_audio: None, + } + } + + fn token(id: &str) -> MediaFileToken { + MediaFileToken::new(id.to_string()) + } + + #[test] + fn passes_when_model_is_none() { + let req = base_request(); + assert!(validate_when_image_required(&req).is_ok()); + } + + #[test] + fn passes_for_other_models_without_image() { + // Only v1.5 needs the gate; other models can do text-to-video. + let req = OmniGenVideoCostAndGenerateRequest { + model: Some(CommonVideoModel::Seedance2p0), + ..base_request() + }; + assert!(validate_when_image_required(&req).is_ok()); + } + + #[test] + fn rejects_v1p5_with_no_image_inputs() { + let req = OmniGenVideoCostAndGenerateRequest { + model: Some(CommonVideoModel::GrokImagineVideo1p5), + ..base_request() + }; + let err = validate_when_image_required(&req).expect_err("should reject"); + match err { + CommonWebError::BadInputWithSimpleMessage(msg) => { + assert_eq!(msg, "Image is required"); + } + other => panic!("expected BadInputWithSimpleMessage, got {:?}", other), + } + } + + #[test] + fn rejects_v1p5_with_empty_reference_list_and_no_start_frame() { + let req = OmniGenVideoCostAndGenerateRequest { + model: Some(CommonVideoModel::GrokImagineVideo1p5), + reference_image_media_tokens: Some(vec![]), + ..base_request() + }; + assert!(matches!( + validate_when_image_required(&req), + Err(CommonWebError::BadInputWithSimpleMessage(_)) + )); + } + + #[test] + fn accepts_v1p5_with_start_frame_only() { + let req = OmniGenVideoCostAndGenerateRequest { + model: Some(CommonVideoModel::GrokImagineVideo1p5), + start_frame_image_media_token: Some(token("mf_start")), + ..base_request() + }; + assert!(validate_when_image_required(&req).is_ok()); + } + + #[test] + fn accepts_v1p5_with_reference_images_only() { + let req = OmniGenVideoCostAndGenerateRequest { + model: Some(CommonVideoModel::GrokImagineVideo1p5), + reference_image_media_tokens: Some(vec![token("mf_a"), token("mf_b")]), + ..base_request() + }; + assert!(validate_when_image_required(&req).is_ok()); + } + + #[test] + fn accepts_v1p5_with_both_start_frame_and_reference_images() { + let req = OmniGenVideoCostAndGenerateRequest { + model: Some(CommonVideoModel::GrokImagineVideo1p5), + start_frame_image_media_token: Some(token("mf_start")), + reference_image_media_tokens: Some(vec![token("mf_a")]), + ..base_request() + }; + assert!(validate_when_image_required(&req).is_ok()); + } + + #[test] + fn end_frame_alone_does_not_count_as_image_input() { + // end_frame_image_media_token is technically an image, but xAI's v1.5 + // wire shape only uses start_frame / reference_images. end_frame + // shouldn't satisfy the gate. + let req = OmniGenVideoCostAndGenerateRequest { + model: Some(CommonVideoModel::GrokImagineVideo1p5), + end_frame_image_media_token: Some(token("mf_end")), + ..base_request() + }; + assert!(validate_when_image_required(&req).is_err()); + } +} diff --git a/crates/service/web/storyteller_web/src/http_server/mod.rs b/crates/service/web/storyteller_web/src/http_server/mod.rs index 82a70137ab..c4aeed0e92 100644 --- a/crates/service/web/storyteller_web/src/http_server/mod.rs +++ b/crates/service/web/storyteller_web/src/http_server/mod.rs @@ -7,5 +7,6 @@ pub mod middleware; pub mod requests; pub mod routes; pub mod session; +pub mod user_lookup; pub mod validations; pub mod web_utils; \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/http_server/routes/application_routes/add_application_routes.rs b/crates/service/web/storyteller_web/src/http_server/routes/application_routes/add_application_routes.rs index e3051809a7..3d0fddc7dc 100644 --- a/crates/service/web/storyteller_web/src/http_server/routes/application_routes/add_application_routes.rs +++ b/crates/service/web/storyteller_web/src/http_server/routes/application_routes/add_application_routes.rs @@ -11,6 +11,7 @@ use crate::http_server::routes::application_routes::generate_routes::add_generat use crate::http_server::routes::application_routes::job_routes::add_job_routes; use crate::http_server::routes::application_routes::media_files_routes::add_media_file_routes; use crate::http_server::routes::application_routes::moderation_routes::add_moderator_routes; +use crate::http_server::routes::application_routes::omni_api_routes::add_omni_api_routes; use crate::http_server::routes::application_routes::omni_gen_routes::add_omni_gen_routes; use crate::http_server::routes::application_routes::prompts_routes::add_prompts_routes; use crate::http_server::routes::application_routes::stripe_artcraft_routes::add_stripe_artcraft_routes; @@ -48,6 +49,7 @@ where // Artcraft surface area app = add_generate_routes(app); // /v1/generate/... app = add_omni_gen_routes(app); // /v1/omni_gen/... + app = add_omni_api_routes(app); // /v1/omni_api/... (API-key authenticated generate) app = add_character_routes(app); // /v1/character/... and /v1/characters/... app = add_webhook_routes(app); // /v1/webhooks/... (fal) diff --git a/crates/service/web/storyteller_web/src/http_server/routes/application_routes/mod.rs b/crates/service/web/storyteller_web/src/http_server/routes/application_routes/mod.rs index e7f8b9a836..80564ab5ae 100644 --- a/crates/service/web/storyteller_web/src/http_server/routes/application_routes/mod.rs +++ b/crates/service/web/storyteller_web/src/http_server/routes/application_routes/mod.rs @@ -11,6 +11,7 @@ mod generate_routes; mod job_routes; mod media_files_routes; mod moderation_routes; +mod omni_api_routes; mod omni_gen_routes; mod prompts_routes; mod stripe_artcraft_routes; diff --git a/crates/service/web/storyteller_web/src/http_server/routes/application_routes/omni_api_routes.rs b/crates/service/web/storyteller_web/src/http_server/routes/application_routes/omni_api_routes.rs new file mode 100644 index 0000000000..a889d7b66d --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/routes/application_routes/omni_api_routes.rs @@ -0,0 +1,36 @@ +use actix_http::body::MessageBody; +use actix_service::ServiceFactory; +use actix_web::dev::{ServiceRequest, ServiceResponse}; +use actix_web::{web, App, Error, HttpResponse}; + +use crate::http_server::endpoints::omni_api::generate::image::omni_api_image_generate_handler::omni_api_image_generate_handler; +use crate::http_server::endpoints::omni_api::generate::video::omni_api_video_generate_handler::omni_api_video_generate_handler; + +/// API-key authenticated generation endpoints. These mirror `/v1/omni_gen/generate/*` but read the +/// caller's identity from the `Authorization` header API key instead of a session cookie. The +/// `cost` and `models` endpoints are not duplicated here — they need no user and the existing +/// `/v1/omni_gen/{cost,models}/*` routes suffice. +pub fn add_omni_api_routes(app: App) -> App +where + B: MessageBody, + T: ServiceFactory< + ServiceRequest, + Config = (), + Response = ServiceResponse, + Error = Error, + InitError = (), + >, +{ + app.service(web::scope("/v1/omni_api") + .service(web::scope("/generate") + .service(web::resource("/video") + .route(web::post().to(omni_api_video_generate_handler)) + .route(web::head().to(|| HttpResponse::Ok())) + ) + .service(web::resource("/image") + .route(web::post().to(omni_api_image_generate_handler)) + .route(web::head().to(|| HttpResponse::Ok())) + ) + ) + ) +} diff --git a/crates/service/web/storyteller_web/src/http_server/user_lookup/api_keys/mod.rs b/crates/service/web/storyteller_web/src/http_server/user_lookup/api_keys/mod.rs new file mode 100644 index 0000000000..c84538ff4f --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/user_lookup/api_keys/mod.rs @@ -0,0 +1 @@ +pub mod require_api_key_user; diff --git a/crates/service/web/storyteller_web/src/http_server/user_lookup/api_keys/require_api_key_user.rs b/crates/service/web/storyteller_web/src/http_server/user_lookup/api_keys/require_api_key_user.rs new file mode 100644 index 0000000000..e7a867bd34 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/user_lookup/api_keys/require_api_key_user.rs @@ -0,0 +1,83 @@ +use std::marker::PhantomData; + +use actix_web::HttpRequest; +use log::{info, warn}; +use sqlx::MySqlConnection; + +use mysql_queries::queries::api_keys::get_api_key_user_by_value::{ + get_api_key_user_by_value, GetApiKeyUserByValueArgs, +}; +use tokens::tokens::api_keys::ApiKeyToken; +use tokens::tokens::users::UserToken; + +use crate::http_server::common_responses::common_web_error::CommonWebError; +use crate::http_server::web_utils::get_authorization_header_api_key::get_authorization_header_api_key; + +/// The authenticated identity behind an API-key request — the API-key analogue of a cookie +/// `SessionUserRecord`, but deliberately minimal (just what the omni-api handlers need). +/// +/// Unlike cookie sessions, this is **never** cached in Redis: every request performs a fresh MySQL +/// lookup. +pub struct UserApiSession { + pub api_key_token: ApiKeyToken, + pub user_token: UserToken, + pub is_banned: bool, +} + +/// Authenticate a request using the API key in its `Authorization` header (see +/// `get_authorization_header_api_key` for the accepted formats). +/// +/// Returns `CommonWebError::NotAuthorized` (401) when the header is missing/unparseable, when the +/// key does not match a live (non-soft-deleted) API key whose owning user still exists, or when +/// that owner is banned. `mysql_executor` can be any sqlx executor — pass `&mut *connection` to +/// reuse a connection the handler already holds. +pub async fn require_api_key_user( + http_request: &HttpRequest, + mysql_connection: &mut MySqlConnection, +) -> Result { + let api_key = match get_authorization_header_api_key(http_request) { + Some(api_key) => api_key, + None => { + warn!("Missing or malformed Authorization header API key"); + return Err(CommonWebError::NotAuthorized); + } + }; + + let maybe_record = get_api_key_user_by_value(GetApiKeyUserByValueArgs { + api_key: &api_key, + mysql_executor: &mut *mysql_connection, + phantom: PhantomData, + }) + .await + .map_err(|err| { + warn!("API key user lookup error: {:?}", err); + CommonWebError::from_error(err) + })?; + + // A missing or soft-deleted key (or a key whose owner no longer exists) is a 401, not a leak of + // which case occurred. + let record = match maybe_record { + Some(record) => record, + None => { + warn!("No live API key user for presented key: {:?}", api_key); + return Err(CommonWebError::NotAuthorized); + } + }; + + if record.is_banned { + warn!("API key owner is banned: {:?}", record.user_token.as_str()); + return Err(CommonWebError::NotAuthorized); + } + + info!( + "API key user authenticated: api_key_token={:?} user_token={:?}", + record.api_key_token.as_str(), + record.user_token.as_str(), + ); + + Ok(UserApiSession { + api_key_token: record.api_key_token, + user_token: record.user_token, + is_banned: record.is_banned, + }) +} diff --git a/crates/service/web/storyteller_web/src/http_server/user_lookup/mod.rs b/crates/service/web/storyteller_web/src/http_server/user_lookup/mod.rs new file mode 100644 index 0000000000..02a4a96543 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/user_lookup/mod.rs @@ -0,0 +1 @@ +pub mod api_keys; From fafb485a6073fac09301808cd5ffda0dd5d1ba4b Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Mon, 15 Jun 2026 22:37:11 -0400 Subject: [PATCH 05/53] omni upload APIs (#1653) --- .../artcraft/artcraft_api_defs/src/lib.rs | 1 + .../artcraft_api_defs/src/omni_api/mod.rs | 3 + .../src/omni_api/omni_upload_audio.rs | 15 + .../src/omni_api/omni_upload_image.rs | 15 + .../src/omni_api/omni_upload_video.rs | 15 + .../web/storyteller_web/src/docs/api_doc.rs | 15 + .../src/http_server/endpoints/omni_api/mod.rs | 1 + .../endpoints/omni_api/upload/mod.rs | 3 + .../omni_upload_audio_media_file_handler.rs | 330 +++++++++++ .../omni_upload_image_media_file_handler.rs | 341 +++++++++++ .../omni_upload_video_media_file_handler.rs | 560 ++++++++++++++++++ .../application_routes/omni_api_routes.rs | 17 + 12 files changed, 1316 insertions(+) create mode 100644 crates/api_clients/artcraft/artcraft_api_defs/src/omni_api/mod.rs create mode 100644 crates/api_clients/artcraft/artcraft_api_defs/src/omni_api/omni_upload_audio.rs create mode 100644 crates/api_clients/artcraft/artcraft_api_defs/src/omni_api/omni_upload_image.rs create mode 100644 crates/api_clients/artcraft/artcraft_api_defs/src/omni_api/omni_upload_video.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/upload/mod.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/upload/omni_upload_audio_media_file_handler.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/upload/omni_upload_image_media_file_handler.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/upload/omni_upload_video_media_file_handler.rs 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 a9d2efc0fb..156cbd21c5 100644 --- a/crates/api_clients/artcraft/artcraft_api_defs/src/lib.rs +++ b/crates/api_clients/artcraft/artcraft_api_defs/src/lib.rs @@ -8,6 +8,7 @@ pub mod generate; pub mod jobs; pub mod media_file; pub mod moderation; +pub mod omni_api; pub mod omni_gen; pub mod prompts; pub mod stripe_artcraft; diff --git a/crates/api_clients/artcraft/artcraft_api_defs/src/omni_api/mod.rs b/crates/api_clients/artcraft/artcraft_api_defs/src/omni_api/mod.rs new file mode 100644 index 0000000000..4cd11c41e5 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_api_defs/src/omni_api/mod.rs @@ -0,0 +1,3 @@ +pub mod omni_upload_audio; +pub mod omni_upload_image; +pub mod omni_upload_video; diff --git a/crates/api_clients/artcraft/artcraft_api_defs/src/omni_api/omni_upload_audio.rs b/crates/api_clients/artcraft/artcraft_api_defs/src/omni_api/omni_upload_audio.rs new file mode 100644 index 0000000000..8c4bb2588f --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_api_defs/src/omni_api/omni_upload_audio.rs @@ -0,0 +1,15 @@ +use serde_derive::{Deserialize, Serialize}; +use utoipa::ToSchema; + +use tokens::tokens::media_files::MediaFileToken; + +/// Success response for the API-key authenticated audio upload endpoint +/// (`POST /v1/omni_api/upload/audio`). +/// +/// NB: The request is a `multipart/form-data` body. Because the multipart form type is bound to the +/// web framework, it is declared alongside the handler in `storyteller_web` rather than here. +#[derive(Clone, Serialize, Deserialize, ToSchema, Debug)] +pub struct OmniUploadAudioMediaFileSuccessResponse { + pub success: bool, + pub media_file_token: MediaFileToken, +} diff --git a/crates/api_clients/artcraft/artcraft_api_defs/src/omni_api/omni_upload_image.rs b/crates/api_clients/artcraft/artcraft_api_defs/src/omni_api/omni_upload_image.rs new file mode 100644 index 0000000000..b8e968d81f --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_api_defs/src/omni_api/omni_upload_image.rs @@ -0,0 +1,15 @@ +use serde_derive::{Deserialize, Serialize}; +use utoipa::ToSchema; + +use tokens::tokens::media_files::MediaFileToken; + +/// Success response for the API-key authenticated image upload endpoint +/// (`POST /v1/omni_api/upload/image`). +/// +/// NB: The request is a `multipart/form-data` body. Because the multipart form type is bound to the +/// web framework, it is declared alongside the handler in `storyteller_web` rather than here. +#[derive(Clone, Serialize, Deserialize, ToSchema, Debug)] +pub struct OmniUploadImageMediaFileSuccessResponse { + pub success: bool, + pub media_file_token: MediaFileToken, +} diff --git a/crates/api_clients/artcraft/artcraft_api_defs/src/omni_api/omni_upload_video.rs b/crates/api_clients/artcraft/artcraft_api_defs/src/omni_api/omni_upload_video.rs new file mode 100644 index 0000000000..da2c606ee5 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_api_defs/src/omni_api/omni_upload_video.rs @@ -0,0 +1,15 @@ +use serde_derive::{Deserialize, Serialize}; +use utoipa::ToSchema; + +use tokens::tokens::media_files::MediaFileToken; + +/// Success response for the API-key authenticated video upload endpoint +/// (`POST /v1/omni_api/upload/video`). +/// +/// NB: The request is a `multipart/form-data` body. Because the multipart form type is bound to the +/// web framework, it is declared alongside the handler in `storyteller_web` rather than here. +#[derive(Clone, Serialize, Deserialize, ToSchema, Debug)] +pub struct OmniUploadVideoMediaFileSuccessResponse { + pub success: bool, + pub media_file_token: MediaFileToken, +} 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 2faf9fe2c9..1eedb13c78 100644 --- a/crates/service/web/storyteller_web/src/docs/api_doc.rs +++ b/crates/service/web/storyteller_web/src/docs/api_doc.rs @@ -80,6 +80,9 @@ use crate::http_server::endpoints::media_files::upload::upload_spz_media_file_ha use crate::http_server::endpoints::media_files::upload::upload_studio_shot::upload_studio_shot_media_file_handler::*; use crate::http_server::endpoints::media_files::upload::upload_video_new::upload_new_video_media_file_handler::*; use crate::http_server::endpoints::media_files::upload::upload_video_old::upload_video_media_file_handler::*; +use crate::http_server::endpoints::omni_api::upload::omni_upload_audio_media_file_handler::*; +use crate::http_server::endpoints::omni_api::upload::omni_upload_image_media_file_handler::*; +use crate::http_server::endpoints::omni_api::upload::omni_upload_video_media_file_handler::*; use crate::http_server::endpoints::media_files::upsert_upload::write_engine_asset::write_engine_asset_media_file_handler::*; 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::*; @@ -343,6 +346,9 @@ use artcraft_api_defs::omni_gen::generate_response::omni_gen_image_generate_resp use artcraft_api_defs::omni_gen::generate_response::omni_gen_video_generate_response::*; use artcraft_api_defs::omni_gen::models::omni_gen_image_models::*; use artcraft_api_defs::omni_gen::models::omni_gen_video_models::*; +use artcraft_api_defs::omni_api::omni_upload_audio::*; +use artcraft_api_defs::omni_api::omni_upload_image::*; +use artcraft_api_defs::omni_api::omni_upload_video::*; use artcraft_api_defs::moderation::user::list_subscribing_users_by_signup_date::*; use artcraft_api_defs::moderation::user::list_users_by_signup_date::*; use artcraft_api_defs::moderation::user::user_lookup::*; @@ -553,6 +559,9 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux // Omni API (API-key authenticated generate) crate::http_server::endpoints::omni_api::generate::image::omni_api_image_generate_handler::omni_api_image_generate_handler, crate::http_server::endpoints::omni_api::generate::video::omni_api_video_generate_handler::omni_api_video_generate_handler, + crate::http_server::endpoints::omni_api::upload::omni_upload_audio_media_file_handler::omni_upload_audio_media_file_handler, + crate::http_server::endpoints::omni_api::upload::omni_upload_image_media_file_handler::omni_upload_image_media_file_handler, + crate::http_server::endpoints::omni_api::upload::omni_upload_video_media_file_handler::omni_upload_video_media_file_handler, // Moderation crate::http_server::endpoints::moderation::alerts::moderation_send_alert_handler::moderation_send_alert_handler, crate::http_server::endpoints::moderation::info::moderator_token_info_handler::moderator_get_token_info_handler, @@ -1080,6 +1089,12 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux UploadEngineAssetMediaSuccessResponse, UploadImageMediaFileForm, UploadImageMediaFileSuccessResponse, + OmniUploadAudioMediaFileForm, + OmniUploadAudioMediaFileSuccessResponse, + OmniUploadImageMediaFileForm, + OmniUploadImageMediaFileSuccessResponse, + OmniUploadVideoMediaFileForm, + OmniUploadVideoMediaFileSuccessResponse, UserImpersonationRequestResponse, UploadMediaSuccessResponse, UploadNewEngineAssetFileForm, diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/mod.rs index 5cd6f81640..46c5a37d41 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/mod.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/mod.rs @@ -1,2 +1,3 @@ pub mod generate; pub mod shared_utils; +pub mod upload; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/upload/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/upload/mod.rs new file mode 100644 index 0000000000..84dfcb12f9 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/upload/mod.rs @@ -0,0 +1,3 @@ +pub mod omni_upload_audio_media_file_handler; +pub mod omni_upload_image_media_file_handler; +pub mod omni_upload_video_media_file_handler; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/upload/omni_upload_audio_media_file_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/upload/omni_upload_audio_media_file_handler.rs new file mode 100644 index 0000000000..b1e444adae --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/upload/omni_upload_audio_media_file_handler.rs @@ -0,0 +1,330 @@ +use std::collections::HashSet; +use std::io::Read; +use std::path::PathBuf; +use std::sync::Arc; + +use actix_multipart::form::tempfile::TempFile; +use actix_multipart::form::text::Text; +use actix_multipart::form::MultipartForm; +use actix_web::web::Json; +use actix_web::{web, HttpRequest}; +use log::{error, info, warn}; +use once_cell::sync::Lazy; +use utoipa::ToSchema; + +use artcraft_api_defs::omni_api::omni_upload_audio::OmniUploadAudioMediaFileSuccessResponse; +use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath; +use enums::by_table::media_files::media_file_class::MediaFileClass; +use enums::by_table::media_files::media_file_type::MediaFileType; +use enums::common::visibility::Visibility; +use hashing::sha256::sha256_hash_bytes::sha256_hash_bytes; +use http_server_common::request::get_request_ip::get_request_ip; +use media::decode_basic_audio_info::decode_basic_audio_bytes_info; +use mimetypes::mimetype_for_bytes::get_mimetype_for_bytes; +use mimetypes::mimetype_to_extension::mimetype_to_extension; +use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token; +use mysql_queries::queries::media_files::create::specialized_insert::insert_media_file_from_file_upload::{insert_media_file_from_file_upload, InsertMediaFileFromUploadArgs, UploadType}; + +use crate::http_server::common_responses::common_web_error::CommonWebError; +use crate::http_server::endpoints::media_files::upload::upload_error::MediaFileUploadError; +use crate::http_server::endpoints::media_files::upload::common_utils::try_parse_generation_provider::try_parse_generation_provider; +use crate::http_server::user_lookup::api_keys::require_api_key_user::require_api_key_user; +use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; +use crate::state::server_state::ServerState; + +/// Form-multipart request fields. +/// +/// IF VIEWING DOCS, PLEASE SEE BOTTOM OF PAGE `OmniUploadAudioMediaFileForm` (Under "Schema") FOR DETAILS ON FIELDS AND NULLABILITY. +#[derive(MultipartForm, ToSchema)] +#[multipart(duplicate_field = "deny")] +pub struct OmniUploadAudioMediaFileForm { + /// UUID for request idempotency + #[multipart(limit = "2 KiB")] + #[schema(value_type = String, format = Binary)] + uuid_idempotency_token: Text, + + /// The uploaded file + #[multipart(limit = "512 MiB")] + #[schema(value_type = Vec, format = Binary)] + file: TempFile, + + /// Optional: Title (name) of the scene + #[multipart(limit = "2 KiB")] + #[schema(value_type = Option, format = Binary)] + maybe_title: Option>, + + /// Optional: Visibility of the scene + #[multipart(limit = "2 KiB")] + #[schema(value_type = Option, format = Binary)] + maybe_visibility: Option>, + + /// Optional: The third-party generation provider (e.g. "fal", "replicate"). + /// If set, `is_user_upload` will be false and `is_intermediate_system_file` will be forced false. + #[multipart(limit = "2 KiB")] + #[schema(value_type = Option, format = Binary)] + maybe_generation_provider: Option>, +} + +static ALLOWED_MIME_TYPES : Lazy> = Lazy::new(|| { + HashSet::from([ + // Audio + "audio/aac", + "audio/m4a", + "audio/mpeg", + "audio/ogg", + "audio/opus", + "audio/x-flac", + "audio/x-wav", + // Mixed + "audio/mp4", // iPhone seems to upload these as audio + // Video + "video/mp4", + "video/webm", + ]) +}); + +/// API-key authenticated audio upload (Omni API). Identity is read from the `Authorization` header +/// API key rather than a session cookie. +#[utoipa::path( + post, + tag = "Omni API", + path = "/v1/omni_api/upload/audio", + responses( + (status = 200, description = "Success Update", body = OmniUploadAudioMediaFileSuccessResponse), + (status = 400, description = "Bad input", body = MediaFileUploadError), + (status = 401, description = "Not authorized", body = MediaFileUploadError), + (status = 429, description = "Too many requests", body = MediaFileUploadError), + (status = 500, description = "Server error", body = MediaFileUploadError), + ), + params( + ( + "request" = OmniUploadAudioMediaFileForm, + description = "IF VIEWING DOCS, PLEASE SEE BOTTOM OF PAGE `OmniUploadAudioMediaFileForm` (Under 'Schema') FOR DETAILS ON FIELDS AND NULLABILITY." + ), + ) +)] +pub async fn omni_upload_audio_media_file_handler( + http_request: HttpRequest, + server_state: web::Data>, + MultipartForm(mut form): MultipartForm, +) -> Result, MediaFileUploadError> { + + let mut mysql_connection = server_state.mysql_pool + .acquire() + .await + .map_err(|err| { + error!("MySql pool error: {:?}", err); + MediaFileUploadError::ServerError + })?; + + // ==================== API KEY USER ==================== // + + // API-key authentication (Authorization header) instead of a session cookie. Never cached, and a + // banned owner is rejected inside `require_api_key_user`. + let api_session = require_api_key_user(&http_request, &mut *mysql_connection) + .await + .map_err(map_api_key_auth_error)?; + + let maybe_user_token = Some(&api_session.user_token); + + let maybe_avt_token = server_state + .avt_cookie_manager + .get_avt_token_from_request(&http_request); + + // ==================== RATE LIMIT ==================== // + + let rate_limiter = &server_state.redis_rate_limiters.file_upload_logged_in; + + if let Err(_err) = rate_limiter.rate_limit_request(&http_request).await { + return Err(MediaFileUploadError::RateLimited); + } + + // ==================== HANDLE IDEMPOTENCY ==================== // + + let uuid_idempotency_token = form.uuid_idempotency_token.as_ref(); + + if let Err(reason) = validate_idempotency_token_format(uuid_idempotency_token) { + return Err(MediaFileUploadError::BadInput(reason)); + } + + insert_idempotency_token(uuid_idempotency_token, &mut *mysql_connection) + .await + .map_err(|err| { + error!("Error inserting idempotency token: {:?}", err); + MediaFileUploadError::BadInput("invalid idempotency token".to_string()) + })?; + + // ==================== UPLOAD METADATA ==================== // + + let maybe_title = form.maybe_title + .map(|title| title.trim().to_string()) + .filter(|title| !title.is_empty()); + + let creator_set_visibility = form.maybe_visibility + .map(|visibility| visibility.0) + .unwrap_or(Visibility::default()); + + // ==================== USER DATA ==================== // + + let ip_address = get_request_ip(&http_request); + + // ==================== FILE VALIDATION ==================== // + + let mut file_bytes = Vec::new(); + form.file.file.read_to_end(&mut file_bytes) + .map_err(|e| { + error!("Problem reading file: {:?}", e); + MediaFileUploadError::ServerError + })?; + + let mimetype = get_mimetype_for_bytes(file_bytes.as_ref()) + .map(|mimetype| mimetype.to_string()) + .ok_or_else(|| { + warn!("Could not determine mimetype for file"); + MediaFileUploadError::BadInput("Could not determine mimetype for file".to_string()) + })?; + + if !ALLOWED_MIME_TYPES.contains(mimetype.as_str()) { + // NB: Don't let our error message inject malicious strings + let filtered_mimetype = mimetype + .chars() + .filter(|c| c.is_ascii()) + .filter(|c| c.is_alphanumeric() || *c == '/') + .collect::(); + return Err(MediaFileUploadError::BadInput(format!("unpermitted mime type: {}", &filtered_mimetype))); + } + + // ==================== DURATION DETECTION ==================== // + + let do_audio_decode = match mimetype.as_ref() { + // TODO: Revisit when Safari can send us this metadata consistently + "audio/mp4" | "video/mp4" => false, + // Symphonia is currently broken for Firefox's opus; skip decoding it. + "audio/opus" => false, + // Also, don't decode images + "image/jpeg" => false, + "image/png" => false, + "image/webp" => false, + _ => true, + }; + + let mut maybe_duration_millis = None; + + if do_audio_decode { + let basic_info = decode_basic_audio_bytes_info( + file_bytes.as_ref(), + Some(&mimetype), + None + ).map_err(|e| { + warn!("file decoding error: {:?}", e); + MediaFileUploadError::BadInput("could not decode file".to_string()) + })?; + + maybe_duration_millis = basic_info.duration_millis; + } + + // ==================== OTHER FILE METADATA ==================== // + + let maybe_filename = form.file.file_name.as_deref() + .as_deref() + .map(|filename| PathBuf::from(filename)); + + let extension = mimetype_to_extension(&mimetype) + .or_else(|| { + maybe_filename + .as_ref() + .and_then(|filename| filename.extension()) + .and_then(|ext| ext.to_str()) + }) + .ok_or_else(|| { + warn!("Could not determine file extension for mimetype: {}", &mimetype); + MediaFileUploadError::ServerError + })?; + + let extension = format!(".{extension}"); // NB: needs dot prefix + + let file_size_bytes = file_bytes.len(); + + let hash = sha256_hash_bytes(&file_bytes) + .map_err(|io_error| { + error!("Problem hashing bytes: {:?}", io_error); + MediaFileUploadError::ServerError + })?; + + // ==================== UPLOAD AND SAVE ==================== // + + const PREFIX : Option<&str> = Some("aud_"); + + let public_upload_path = MediaFileBucketPath::generate_new(PREFIX, Some(&extension)); + + info!("Uploading media to bucket path: {}", public_upload_path.get_full_object_path_str()); + + server_state.public_bucket_client.upload_file_with_content_type( + public_upload_path.get_full_object_path_str(), + file_bytes.as_ref(), + &mimetype) + .await + .map_err(|e| { + warn!("Upload media bytes to bucket error: {:?}", e); + MediaFileUploadError::ServerError + })?; + + let maybe_generation_provider = form.maybe_generation_provider + .as_ref() + .and_then(|text| try_parse_generation_provider(text.as_ref())); + + let upload_type = if maybe_generation_provider.is_some() { + UploadType::ThirdPartyInference + } else { + UploadType::Filesystem + }; + + let (token, record_id) = insert_media_file_from_file_upload(InsertMediaFileFromUploadArgs { + maybe_media_class: Some(MediaFileClass::Audio), + media_file_type: MediaFileType::Audio, + maybe_creator_user_token: maybe_user_token, + maybe_creator_anonymous_visitor_token: maybe_avt_token.as_ref(), + creator_ip_address: &ip_address, + creator_set_visibility, + upload_type, + maybe_engine_category: None, + maybe_animation_type: None, + maybe_prompt_token: None, + maybe_batch_token: None, + maybe_mime_type: Some(&mimetype), + file_size_bytes: file_size_bytes as u64, + maybe_duration_millis, + sha256_checksum: &hash, + maybe_title: maybe_title.as_deref(), + maybe_scene_source_media_file_token: None, + is_intermediate_system_file: false, // NB: is_user_upload = true + maybe_generation_provider, + public_bucket_directory_hash: public_upload_path.get_object_hash(), + maybe_public_bucket_prefix: PREFIX, + maybe_public_bucket_extension: Some(&extension), + pool: &server_state.mysql_pool, + }) + .await + .map_err(|err| { + warn!("New file creation DB error: {:?}", err); + MediaFileUploadError::ServerError + })?; + + info!("new media file id: {} token: {:?}", record_id, &token); + + Ok(Json(OmniUploadAudioMediaFileSuccessResponse { + success: true, + media_file_token: token, + })) +} + +/// Map an API-key authentication failure onto this endpoint's error type. A genuine 401 stays a +/// 401; anything else (e.g. a DB error during lookup) becomes a 500. +fn map_api_key_auth_error(err: CommonWebError) -> MediaFileUploadError { + match err { + CommonWebError::NotAuthorized => { + MediaFileUploadError::NotAuthorizedVerbose("invalid or missing API key".to_string()) + } + _ => MediaFileUploadError::ServerError, + } +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/upload/omni_upload_image_media_file_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/upload/omni_upload_image_media_file_handler.rs new file mode 100644 index 0000000000..e36f5b63a6 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/upload/omni_upload_image_media_file_handler.rs @@ -0,0 +1,341 @@ +use std::collections::HashSet; +use std::io::Read; +use std::path::PathBuf; +use std::sync::Arc; + +use actix_multipart::form::tempfile::TempFile; +use actix_multipart::form::text::Text; +use actix_multipart::form::MultipartForm; +use actix_web::web::Json; +use actix_web::{web, HttpRequest}; +use log::{error, info, warn}; +use once_cell::sync::Lazy; +use utoipa::ToSchema; + +use artcraft_api_defs::omni_api::omni_upload_image::OmniUploadImageMediaFileSuccessResponse; +use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath; +use enums::by_table::media_files::media_file_class::MediaFileClass; +use enums::by_table::media_files::media_file_type::MediaFileType; +use enums::common::visibility::Visibility; +use hashing::sha256::sha256_hash_bytes::sha256_hash_bytes; +use http_server_common::request::get_request_ip::get_request_ip; +use mimetypes::mimetype_for_bytes::get_mimetype_for_bytes; +use mimetypes::mimetype_to_extension::mimetype_to_extension; +use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token; +use mysql_queries::queries::media_files::create::specialized_insert::insert_media_file_from_file_upload::{insert_media_file_from_file_upload, InsertMediaFileFromUploadArgs, UploadType}; +use tokens::tokens::batch_generations::BatchGenerationToken; +use tokens::tokens::prompts::PromptToken; + +use crate::http_server::common_responses::common_web_error::CommonWebError; +use crate::http_server::endpoints::media_files::upload::upload_error::MediaFileUploadError; +use crate::http_server::endpoints::media_files::upload::common_utils::try_parse_generation_provider::try_parse_generation_provider; +use crate::http_server::user_lookup::api_keys::require_api_key_user::require_api_key_user; +use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; +use crate::state::server_state::ServerState; + +/// Form-multipart request fields. +/// +/// IF VIEWING DOCS, PLEASE SEE BOTTOM OF PAGE `OmniUploadImageMediaFileForm` (Under "Schema") FOR DETAILS ON FIELDS AND NULLABILITY. +#[derive(MultipartForm, ToSchema)] +#[multipart(duplicate_field = "deny")] +pub struct OmniUploadImageMediaFileForm { + /// UUID for request idempotency + #[multipart(limit = "2 KiB")] + #[schema(value_type = String, format = Binary)] + uuid_idempotency_token: Text, + + /// The uploaded file + #[multipart(limit = "512 MiB")] + #[schema(value_type = Vec, format = Binary)] + file: TempFile, + + /// Optional: Title (name) of the scene + #[multipart(limit = "2 KiB")] + #[schema(value_type = Option, format = Binary)] + maybe_title: Option>, + + /// Optional: Visibility of the scene + #[multipart(limit = "2 KiB")] + #[schema(value_type = Option, format = Binary)] + maybe_visibility: Option>, + + /// Optional: Prompt associated with this image + /// NOTE: Cannot set `is_intermediate_system_file = true` if this is set. + #[multipart(limit = "2 KiB")] + #[schema(value_type = Option, format = Binary)] + maybe_prompt_token: Option>, + + /// Optional: Batch associated with this image + /// NOTE: Cannot set `is_intermediate_system_file = true` if this is set. + #[multipart(limit = "2 KiB")] + #[schema(value_type = Option, format = Binary)] + maybe_batch_token: Option>, + + /// Optional: Whether this is a system file (e.g. cover files we should hide) + #[multipart(limit = "2 KiB")] + #[schema(value_type = Option, format = Binary)] + is_intermediate_system_file: Option>, + + /// Optional: The third-party generation provider (e.g. "fal", "replicate"). + /// If set, `is_user_upload` will be false and `is_intermediate_system_file` will be forced false. + #[multipart(limit = "2 KiB")] + #[schema(value_type = Option, format = Binary)] + maybe_generation_provider: Option>, +} + +static ALLOWED_MIME_TYPES : Lazy> = Lazy::new(|| { + HashSet::from([ + "image/jpeg", + "image/png", + "image/gif", + "image/webp", + ]) +}); + +/// API-key authenticated image upload (Omni API). Identity is read from the `Authorization` header +/// API key rather than a session cookie. +#[utoipa::path( + post, + tag = "Omni API", + path = "/v1/omni_api/upload/image", + responses( + (status = 200, description = "Success Update", body = OmniUploadImageMediaFileSuccessResponse), + (status = 400, description = "Bad input", body = MediaFileUploadError), + (status = 401, description = "Not authorized", body = MediaFileUploadError), + (status = 429, description = "Too many requests", body = MediaFileUploadError), + (status = 500, description = "Server error", body = MediaFileUploadError), + ), + params( + ( + "request" = OmniUploadImageMediaFileForm, + description = "IF VIEWING DOCS, PLEASE SEE BOTTOM OF PAGE `OmniUploadImageMediaFileForm` (Under 'Schema') FOR DETAILS ON FIELDS AND NULLABILITY." + ), + ) +)] +pub async fn omni_upload_image_media_file_handler( + http_request: HttpRequest, + server_state: web::Data>, + MultipartForm(mut form): MultipartForm, +) -> Result, MediaFileUploadError> { + + validate_form(&form)?; + + let mut mysql_connection = server_state.mysql_pool + .acquire() + .await + .map_err(|err| { + error!("MySql pool error: {:?}", err); + MediaFileUploadError::ServerError + })?; + + // ==================== API KEY USER ==================== // + + // API-key authentication (Authorization header) instead of a session cookie. Never cached, and a + // banned owner is rejected inside `require_api_key_user`. + let api_session = require_api_key_user(&http_request, &mut *mysql_connection) + .await + .map_err(map_api_key_auth_error)?; + + let maybe_user_token = Some(&api_session.user_token); + + let maybe_avt_token = server_state + .avt_cookie_manager + .get_avt_token_from_request(&http_request); + + // ==================== HANDLE IDEMPOTENCY ==================== // + + let uuid_idempotency_token = form.uuid_idempotency_token.as_ref(); + + if let Err(reason) = validate_idempotency_token_format(uuid_idempotency_token) { + return Err(MediaFileUploadError::BadInput(reason)); + } + + insert_idempotency_token(uuid_idempotency_token, &mut *mysql_connection) + .await + .map_err(|err| { + error!("Error inserting idempotency token: {:?}", err); + MediaFileUploadError::BadInput("invalid idempotency token".to_string()) + })?; + + // ==================== UPLOAD METADATA ==================== // + + let maybe_title = form.maybe_title.map(|title| title.to_string()); + + let creator_set_visibility = form.maybe_visibility + .map(|visibility| visibility.0) + .unwrap_or(Visibility::default()); + + // ==================== USER DATA ==================== // + + let ip_address = get_request_ip(&http_request); + + // ==================== FILE VALIDATION ==================== // + + let mut file_bytes = Vec::new(); + form.file.file.read_to_end(&mut file_bytes) + .map_err(|e| { + error!("Problem reading file: {:?}", e); + MediaFileUploadError::ServerError + })?; + + let mimetype = get_mimetype_for_bytes(file_bytes.as_ref()) + .map(|mimetype| mimetype.to_string()) + .ok_or_else(|| { + warn!("Could not determine mimetype for file"); + MediaFileUploadError::BadInput("Could not determine mimetype for file".to_string()) + })?; + + if !ALLOWED_MIME_TYPES.contains(mimetype.as_str()) { + // NB: Don't let our error message inject malicious strings + let filtered_mimetype = mimetype + .chars() + .filter(|c| c.is_ascii()) + .filter(|c| c.is_alphanumeric() || *c == '/') + .collect::(); + return Err(MediaFileUploadError::BadInput(format!("unpermitted mime type: {}", &filtered_mimetype))); + } + + // ==================== OTHER FILE METADATA ==================== // + + let maybe_filename = form.file.file_name.as_deref() + .as_deref() + .map(|filename| PathBuf::from(filename)); + + let extension = mimetype_to_extension(&mimetype) + .or_else(|| { + maybe_filename + .as_ref() + .and_then(|filename| filename.extension()) + .and_then(|ext| ext.to_str()) + }) + .ok_or_else(|| { + warn!("Could not determine file extension for mimetype: {}", &mimetype); + MediaFileUploadError::ServerError + })?; + + let extension = format!(".{extension}"); // NB: needs dot prefix + + let file_size_bytes = file_bytes.len(); + + let hash = sha256_hash_bytes(&file_bytes) + .map_err(|io_error| { + error!("Problem hashing bytes: {:?}", io_error); + MediaFileUploadError::ServerError + })?; + + // ==================== UPLOAD AND SAVE ==================== // + + const PREFIX : Option<&str> = Some("image_"); + + let public_upload_path = MediaFileBucketPath::generate_new(PREFIX, Some(&extension)); + + info!("Uploading media to bucket path: {}", public_upload_path.get_full_object_path_str()); + + server_state.public_bucket_client.upload_file_with_content_type( + public_upload_path.get_full_object_path_str(), + file_bytes.as_ref(), + &mimetype) + .await + .map_err(|e| { + warn!("Upload media bytes to bucket error: {:?}", e); + MediaFileUploadError::ServerError + })?; + + let is_intermediate_system_file = form.is_intermediate_system_file + .map(|b| b.0) + .unwrap_or(false); + + let maybe_prompt_token = form.maybe_prompt_token + .as_ref() + .map(|token| token.0.clone()); + + let maybe_batch_token = form.maybe_batch_token + .as_ref() + .map(|token| token.0.clone()); + + let maybe_generation_provider = form.maybe_generation_provider + .as_ref() + .and_then(|text| try_parse_generation_provider(text.as_ref())); + + let upload_type = if maybe_generation_provider.is_some() { + UploadType::ThirdPartyInference + } else { + UploadType::Filesystem + }; + + let media_file_type = match mimetype.as_str() { + "image/jpeg" => MediaFileType::Jpg, + "image/png" => MediaFileType::Png, + "image/gif" => MediaFileType::Gif, + "image/webp" => MediaFileType::Image, // Fallback + _ => MediaFileType::Image, // fallback + }; + + let (token, record_id) = insert_media_file_from_file_upload(InsertMediaFileFromUploadArgs { + maybe_media_class: Some(MediaFileClass::Image), + media_file_type, + maybe_creator_user_token: maybe_user_token, + maybe_creator_anonymous_visitor_token: maybe_avt_token.as_ref(), + creator_ip_address: &ip_address, + creator_set_visibility, + upload_type, + maybe_engine_category: None, + maybe_animation_type: None, + maybe_mime_type: Some(&mimetype), + maybe_prompt_token: maybe_prompt_token.as_ref(), + maybe_batch_token: maybe_batch_token.as_ref(), + file_size_bytes: file_size_bytes as u64, + maybe_duration_millis: None, + sha256_checksum: &hash, + maybe_title: maybe_title.as_deref(), + maybe_scene_source_media_file_token: None, + is_intermediate_system_file, + maybe_generation_provider, + public_bucket_directory_hash: public_upload_path.get_object_hash(), + maybe_public_bucket_prefix: PREFIX, + maybe_public_bucket_extension: Some(&extension), + pool: &server_state.mysql_pool, + }) + .await + .map_err(|err| { + warn!("New file creation DB error: {:?}", err); + MediaFileUploadError::ServerError + })?; + + info!("new media file id: {} token: {:?}", record_id, &token); + + Ok(Json(OmniUploadImageMediaFileSuccessResponse { + success: true, + media_file_token: token, + })) +} + +fn validate_form(form: &OmniUploadImageMediaFileForm) -> Result<(), MediaFileUploadError> { + if form.uuid_idempotency_token.is_empty() { + return Err(MediaFileUploadError::BadInput("Idempotency token is required".to_string())); + } + + let is_intermediate_system_file = form.is_intermediate_system_file + .as_ref() + .map(|b| b.0) + .unwrap_or(false); + + if is_intermediate_system_file && form.maybe_prompt_token.is_some() { + return Err(MediaFileUploadError::BadInput( + "Cannot set `is_intermediate_system_file` to true if `maybe_prompt_token` is provided." + .to_string())); + } + + Ok(()) +} + +/// Map an API-key authentication failure onto this endpoint's error type. A genuine 401 stays a +/// 401; anything else (e.g. a DB error during lookup) becomes a 500. +fn map_api_key_auth_error(err: CommonWebError) -> MediaFileUploadError { + match err { + CommonWebError::NotAuthorized => { + MediaFileUploadError::NotAuthorizedVerbose("invalid or missing API key".to_string()) + } + _ => MediaFileUploadError::ServerError, + } +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/upload/omni_upload_video_media_file_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/upload/omni_upload_video_media_file_handler.rs new file mode 100644 index 0000000000..66b23f6b95 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/upload/omni_upload_video_media_file_handler.rs @@ -0,0 +1,560 @@ +use std::collections::HashSet; +use std::io::Read; +use std::path::PathBuf; +use std::sync::Arc; +use std::time::Duration; + +use actix_multipart::form::tempfile::TempFile; +use actix_multipart::form::text::Text; +use actix_multipart::form::MultipartForm; +use actix_web::web::Json; +use actix_web::{web, HttpRequest}; +use log::{debug, error, info, warn}; +use once_cell::sync::Lazy; +use utoipa::ToSchema; + +use artcraft_api_defs::omni_api::omni_upload_video::OmniUploadVideoMediaFileSuccessResponse; +use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath; +use enums::by_table::media_files::media_file_class::MediaFileClass; +use enums::by_table::media_files::media_file_type::MediaFileType; +use enums::common::visibility::Visibility; +use ffmpeg_utils::ffmpeg::ffmpeg_transcode_to_mp4::{ffmpeg_transcode_to_mp4, FfmpegTranscodeToMp4Args}; +use ffmpeg_utils::ffmpeg::ffmpeg_trim_and_resample::{ffmpeg_trim_and_resample, Args}; +use ffmpeg_utils::ffprobe::ffprobe_get_info::ffprobe_get_info; +use filesys::file_read_bytes::file_read_bytes; +use filesys::path_to_string::path_to_string; +use hashing::sha256::sha256_hash_bytes::sha256_hash_bytes; +use http_server_common::request::get_request_ip::get_request_ip; +use mimetypes::mimetype_for_bytes::get_mimetype_for_bytes; +use mimetypes::mimetype_to_extension::mimetype_to_extension; +use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token; +use mysql_queries::queries::media_files::create::specialized_insert::insert_media_file_from_file_upload::{insert_media_file_from_file_upload, InsertMediaFileFromUploadArgs, UploadType}; +use thumbnail_generator::task_client::thumbnail_task::{ThumbnailTaskBuilder, ThumbnailTaskInputMimeType}; +use tokens::tokens::media_files::MediaFileToken; +use tokens::tokens::prompts::PromptToken; + +use crate::http_server::common_responses::common_web_error::CommonWebError; +use crate::http_server::endpoints::media_files::upload::upload_error::MediaFileUploadError; +use crate::http_server::endpoints::media_files::upload::common_utils::try_parse_generation_provider::try_parse_generation_provider; +use crate::http_server::user_lookup::api_keys::require_api_key_user::require_api_key_user; +use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; +use crate::state::server_state::ServerState; + +/// Form-multipart request fields. +/// +/// IF VIEWING DOCS, PLEASE SEE BOTTOM OF PAGE `OmniUploadVideoMediaFileForm` (Under "Schema") FOR DETAILS ON FIELDS AND NULLABILITY. +#[derive(MultipartForm, ToSchema)] +#[multipart(duplicate_field = "deny")] +pub struct OmniUploadVideoMediaFileForm { + /// UUID for request idempotency + #[multipart(limit = "2 KiB")] + #[schema(value_type = String, format = Binary)] + uuid_idempotency_token: Text, + + /// The uploaded file + #[multipart(limit = "512 MiB")] + #[schema(value_type = Vec, format = Binary)] + file: TempFile, + + /// Optional: Title (name) of the scene + #[multipart(limit = "2 KiB")] + #[schema(value_type = Option, format = Binary)] + maybe_title: Option>, + + /// Optional: Visibility of the scene + #[multipart(limit = "2 KiB")] + #[schema(value_type = Option, format = Binary)] + maybe_visibility: Option>, + + /// Optional: Prompt associated with this image + /// NOTE: Cannot set `is_intermediate_system_file = true` if this is set. + #[multipart(limit = "2 KiB")] + #[schema(value_type = Option, format = Binary)] + maybe_prompt_token: Option>, + + /// Optional: If an engine scene was used to generate this video, provide it here to create a link. + #[multipart(limit = "2 KiB")] + #[schema(value_type = Option, format = Binary)] + maybe_scene_source_media_file_token: Option>, + + /// Optional: Whether this is a system file (eg. cover files we should hide) + #[multipart(limit = "2 KiB")] + #[schema(value_type = Option, format = Binary)] + is_intermediate_system_file: Option>, + + /// Optional: Trim start offset in milliseconds. + #[multipart(limit = "2 KiB")] + #[schema(value_type = Option, format = Binary)] + maybe_trim_start_millis: Option>, + + /// Optional: Trim end offset in milliseconds. + #[multipart(limit = "2 KiB")] + #[schema(value_type = Option, format = Binary)] + maybe_trim_end_millis: Option>, + + /// Optional: Resample the video to this FPS. + /// Only certain values equal or under 24fps are allowed. + #[multipart(limit = "2 KiB")] + #[schema(value_type = Option, format = Binary)] + maybe_resample_fps: Option>, + + /// Optional: The third-party generation provider (e.g. "fal", "replicate"). + /// If set, `is_user_upload` will be false and `is_intermediate_system_file` will be forced false. + #[multipart(limit = "2 KiB")] + #[schema(value_type = Option, format = Binary)] + maybe_generation_provider: Option>, +} + +static ALLOWED_MIME_TYPES : Lazy> = Lazy::new(|| { + HashSet::from([ + // Video + "video/mp4", // NB: Only mp4 for now. + ]) +}); + +static TRANSCODE_MIME_TYPES : Lazy> = Lazy::new(|| { + HashSet::from([ + // Video + "video/quicktime", // .qt quicktime files + ]) +}); + +/// API-key authenticated video upload (Omni API). Identity is read from the `Authorization` header +/// API key rather than a session cookie. +#[utoipa::path( + post, + tag = "Omni API", + path = "/v1/omni_api/upload/video", + responses( + (status = 200, description = "Success Update", body = OmniUploadVideoMediaFileSuccessResponse), + (status = 400, description = "Bad input", body = MediaFileUploadError), + (status = 401, description = "Not authorized", body = MediaFileUploadError), + (status = 429, description = "Too many requests", body = MediaFileUploadError), + (status = 500, description = "Server error", body = MediaFileUploadError), + ), + params( + ( + "request" = OmniUploadVideoMediaFileForm, + description = "IF VIEWING DOCS, PLEASE SEE BOTTOM OF PAGE `OmniUploadVideoMediaFileForm` (Under 'Schema') FOR DETAILS ON FIELDS AND NULLABILITY." + ), + ) +)] +pub async fn omni_upload_video_media_file_handler( + http_request: HttpRequest, + server_state: web::Data>, + MultipartForm(mut form): MultipartForm, +) -> Result, MediaFileUploadError> { + + fast_form_validations(&form)?; + + let mut mysql_connection = server_state.mysql_pool + .acquire() + .await + .map_err(|err| { + error!("MySql pool error: {:?}", err); + MediaFileUploadError::ServerError + })?; + + // ==================== API KEY USER ==================== // + + // API-key authentication (Authorization header) instead of a session cookie. Never cached, and a + // banned owner is rejected inside `require_api_key_user`. + let api_session = require_api_key_user(&http_request, &mut *mysql_connection) + .await + .map_err(map_api_key_auth_error)?; + + let maybe_user_token = Some(&api_session.user_token); + + let maybe_avt_token = server_state + .avt_cookie_manager + .get_avt_token_from_request(&http_request); + + // ==================== RATE LIMIT ==================== // + + let rate_limiter = &server_state.redis_rate_limiters.file_upload_logged_in; + + if let Err(_err) = rate_limiter.rate_limit_request(&http_request).await { + return Err(MediaFileUploadError::RateLimited); + } + + // ==================== HANDLE IDEMPOTENCY ==================== // + + let uuid_idempotency_token = form.uuid_idempotency_token.as_ref(); + + if let Err(reason) = validate_idempotency_token_format(uuid_idempotency_token) { + return Err(MediaFileUploadError::BadInput(reason)); + } + + insert_idempotency_token(uuid_idempotency_token, &mut *mysql_connection) + .await + .map_err(|err| { + error!("Error inserting idempotency token: {:?}", err); + MediaFileUploadError::BadInput("invalid idempotency token".to_string()) + })?; + + // ==================== UPLOAD METADATA ==================== // + + let maybe_title = form.maybe_title + .map(|title| title.trim().to_string()) + .filter(|title| !title.is_empty()); + + let creator_set_visibility = form.maybe_visibility + .map(|visibility| visibility.0) + .unwrap_or(Visibility::default()); + + // ==================== USER DATA ==================== // + + let ip_address = get_request_ip(&http_request); + + // ==================== FILE VALIDATION ==================== // + + let mut file_bytes = Vec::new(); + form.file.file.read_to_end(&mut file_bytes) + .map_err(|e| { + error!("Problem reading file: {:?}", e); + MediaFileUploadError::ServerError + })?; + + let mut mimetype = get_mimetype_for_bytes(file_bytes.as_ref()) + .map(|mimetype| mimetype.to_string()) + .ok_or_else(|| { + warn!("Could not determine mimetype for file"); + MediaFileUploadError::BadInput("Could not determine mimetype for file".to_string()) + })?; + + let needs_transcode = TRANSCODE_MIME_TYPES.contains(mimetype.as_str()); + + if !ALLOWED_MIME_TYPES.contains(mimetype.as_str()) && !needs_transcode { + // NB: Don't let our error message inject malicious strings + let filtered_mimetype = mimetype + .chars() + .filter(|c| c.is_ascii()) + .filter(|c| c.is_alphanumeric() || *c == '/') + .collect::(); + return Err(MediaFileUploadError::BadInput(format!("unpermitted mime type: {}", &filtered_mimetype))); + } + + // ==================== ORIGINAL FILE BOOKKEEPING ==================== // + // Hold on to the user's original file (path + mimetype + extension) so we + // can upload it as a `_original.{ext}` sibling later if we end up + // transcoding and/or resampling it. + + let original_file_path: PathBuf = form.file.file.path().to_path_buf(); + let original_mimetype: String = mimetype.clone(); + let original_filename_extension: Option = form.file.file_name.as_deref() + .and_then(|filename| std::path::Path::new(filename).extension()) + .and_then(|ext| ext.to_str()) + .map(|ext| ext.to_string()); + let original_extension: String = mimetype_to_extension(&original_mimetype) + .map(|ext| ext.to_string()) + .or(original_filename_extension) + .ok_or_else(|| { + warn!("Could not determine file extension for original mimetype: {}", &original_mimetype); + MediaFileUploadError::ServerError + })?; + + // ==================== OPTIONAL TRANSCODE TO MP4 ==================== // + // Some accepted source mime types (e.g. QuickTime .mov) are not the canonical + // distribution format we want for the media file record. Transcode them to + // mp4 in a tempdir so we can resample / upload mp4 instead. + + let mut transcode_tempdir_ref = None; + let mut resample_tempdir_ref = None; + + let mut final_upload_file_path = form.file.file.path().to_path_buf(); + + if needs_transcode { + let transcode_tempdir = server_state.temp_dir_creator.new_tempdir("ffmpeg_transcode") + .map_err(|err| { + error!("Problem creating transcode temp dir: {:?}", err); + MediaFileUploadError::ServerError + })?; + + let transcoded_path = transcode_tempdir.path().join("transcoded.mp4"); + + info!("Transcoding {} → mp4", &original_mimetype); + ffmpeg_transcode_to_mp4(FfmpegTranscodeToMp4Args { + video_input_path: form.file.file.path(), + video_output_path: &transcoded_path, + }).map_err(|err| { + error!("Problem transcoding video to mp4: {:?}", err); + MediaFileUploadError::ServerError + })?; + + file_bytes = file_read_bytes(&transcoded_path) + .map_err(|e| { + error!("Problem reading transcoded mp4: {:?}", e); + MediaFileUploadError::ServerError + })?; + mimetype = "video/mp4".to_string(); + final_upload_file_path = transcoded_path; + transcode_tempdir_ref = Some(transcode_tempdir); + } + + // ==================== OPTIONAL VIDEO RESAMPLE ==================== // + + let should_resample = form.maybe_resample_fps.is_some() + || form.maybe_trim_start_millis.is_some() + || form.maybe_trim_end_millis.is_some(); + + if should_resample { + let frame_temp_dir = server_state.temp_dir_creator.new_tempdir("ffmpeg") + .map_err(|err| { + error!("Problem creating temp dir: {:?}", err); + MediaFileUploadError::ServerError + })?; + + let video_output_path = frame_temp_dir.path().join("output.mp4"); + + let maybe_new_frame_rate = form.maybe_resample_fps.map(|fps| fps.0); + let maybe_start_offset = form.maybe_trim_start_millis.map(|millis| Duration::from_millis(millis.0)); + let maybe_end_offset = form.maybe_trim_end_millis.map(|millis| Duration::from_millis(millis.0)); + + ffmpeg_trim_and_resample(Args { + video_input_path: &final_upload_file_path, + video_output_path: &video_output_path, + maybe_new_frame_rate, + maybe_start_offset, + maybe_end_offset, + }).map_err(|err| { + error!("Problem resampling video: {:?}", err); + MediaFileUploadError::ServerError + })?; + + file_bytes = file_read_bytes(&video_output_path) + .map_err(|e| { + error!("Problem reading file: {:?}", e); + MediaFileUploadError::ServerError + })?; + + mimetype = get_mimetype_for_bytes(file_bytes.as_ref()) + .map(|mimetype| mimetype.to_string()) + .ok_or_else(|| { + warn!("Could not determine mimetype for file"); + MediaFileUploadError::BadInput("Could not determine mimetype for file".to_string()) + })?; + + final_upload_file_path = video_output_path; + resample_tempdir_ref = Some(frame_temp_dir); // NB: Keep from going out of scope + } + + // True if we touched the user's file in any way (transcoded, resampled, or both). + let should_upload_original = needs_transcode || should_resample; + + // ==================== OTHER FILE METADATA ==================== // + + let mut maybe_duration_millis = None; + + match ffprobe_get_info(&final_upload_file_path) { + Ok(video_info) => { + maybe_duration_millis = video_info.duration + .map(|duration| duration.millis as u64); + } + Err(error) => { + warn!("Error reading video dimensions with ffprobe: {:?}", error); + } + } + + let maybe_filename = form.file.file_name.as_deref() + .as_deref() + .map(|filename| PathBuf::from(filename)); + + let extension = mimetype_to_extension(&mimetype) + .or_else(|| { + maybe_filename + .as_ref() + .and_then(|filename| filename.extension()) + .and_then(|ext| ext.to_str()) + }) + .ok_or_else(|| { + warn!("Could not determine file extension for mimetype: {}", &mimetype); + MediaFileUploadError::ServerError + })?; + + let extension = format!(".{extension}"); // NB: needs dot prefix + + let file_size_bytes = file_bytes.len(); + + let hash = sha256_hash_bytes(&file_bytes) + .map_err(|io_error| { + error!("Problem hashing bytes: {:?}", io_error); + MediaFileUploadError::ServerError + })?; + + // ==================== UPLOAD AND SAVE ==================== // + + const PREFIX : Option<&str> = Some("video_"); + + let public_upload_path = MediaFileBucketPath::generate_new(PREFIX, Some(&extension)); + + info!("Uploading media to bucket path: {}", public_upload_path.get_full_object_path_str()); + + server_state.public_bucket_client.upload_file_with_content_type( + public_upload_path.get_full_object_path_str(), + file_bytes.as_ref(), + &mimetype) + .await + .map_err(|e| { + warn!("Upload media bytes to bucket error: {:?}", e); + MediaFileUploadError::ServerError + })?; + + // ==================== UPLOAD ORIGINAL SIBLING ==================== // + // If we transcoded and/or resampled the user's upload, also upload the + // unmodified original alongside the canonical file as `_original.{ext}`. + + if should_upload_original { + let original_suffix = format!("_original.{}", original_extension); + let original_bucket_path = MediaFileBucketPath::from_object_hash( + public_upload_path.get_object_hash(), + PREFIX, + Some(&original_suffix), + ); + + info!( + "Uploading original sibling to bucket path: {}", + original_bucket_path.get_full_object_path_str(), + ); + + let original_bytes = file_read_bytes(&original_file_path) + .map_err(|e| { + error!("Problem reading original file for sibling upload: {:?}", e); + MediaFileUploadError::ServerError + })?; + + server_state.public_bucket_client.upload_file_with_content_type( + original_bucket_path.get_full_object_path_str(), + original_bytes.as_ref(), + &original_mimetype, + ) + .await + .map_err(|e| { + // NB: Sibling failure is non-fatal — the canonical upload already + // succeeded and the user can still use the media. Log loudly. + warn!("Upload original sibling to bucket error: {:?}", e); + }) + .ok(); + } + + let maybe_scene_source_media_file_token = form.maybe_scene_source_media_file_token + .as_ref() + .map(|token| &token.0); + + let maybe_prompt_token = form.maybe_prompt_token + .as_ref() + .map(|token| token.0.clone()); + + // NB: If we're uploading a video file that references an engine scene, then this is an engine + // render video, and we should mark it as a system (hidden) file. + let is_intermediate_system_file = + maybe_scene_source_media_file_token.is_some() || + form.is_intermediate_system_file.map(|b| b.0).unwrap_or(false); + + let maybe_generation_provider = form.maybe_generation_provider + .as_ref() + .and_then(|text| try_parse_generation_provider(text.as_ref())); + + let upload_type = if maybe_generation_provider.is_some() { + UploadType::ThirdPartyInference + } else { + UploadType::Filesystem + }; + + let (token, record_id) = insert_media_file_from_file_upload(InsertMediaFileFromUploadArgs { + maybe_media_class: Some(MediaFileClass::Video), + media_file_type: MediaFileType::Video, + maybe_creator_user_token: maybe_user_token, + maybe_creator_anonymous_visitor_token: maybe_avt_token.as_ref(), + creator_ip_address: &ip_address, + creator_set_visibility, + upload_type, + maybe_engine_category: None, + maybe_animation_type: None, + maybe_prompt_token: maybe_prompt_token.as_ref(), + maybe_batch_token: None, + maybe_mime_type: Some(&mimetype), + file_size_bytes: file_size_bytes as u64, + maybe_duration_millis, + sha256_checksum: &hash, + maybe_title: maybe_title.as_deref(), + maybe_scene_source_media_file_token, + is_intermediate_system_file, // NB: is_user_upload = TRUE + maybe_generation_provider, + public_bucket_directory_hash: public_upload_path.get_object_hash(), + maybe_public_bucket_prefix: PREFIX, + maybe_public_bucket_extension: Some(&extension), + pool: &server_state.mysql_pool, + }) + .await + .map_err(|err| { + warn!("New file creation DB error: {:?}", err); + MediaFileUploadError::ServerError + })?; + + info!("new media file id: {} token: {:?}", record_id, &token); + + let thumbnail_task_result = ThumbnailTaskBuilder::new_for_source_mimetype(ThumbnailTaskInputMimeType::MP4) + .with_bucket(server_state.public_bucket_client.bucket_name().as_str()) + .with_path(&*path_to_string(public_upload_path.to_full_object_pathbuf())) + .with_output_suffix("thumb") + .with_event_id(&token.to_string()) + .send_all() + .await; + + match thumbnail_task_result { + Ok(thumbnail_task) => { + debug!("Thumbnail tasks sent: {:?}", thumbnail_task); + }, + Err(e) => { + error!("Failed to create some/all thumbnail tasks: {:?}", e); + } + } + + Ok(Json(OmniUploadVideoMediaFileSuccessResponse { + success: true, + media_file_token: token, + })) +} + +fn fast_form_validations(form: &OmniUploadVideoMediaFileForm) -> Result<(), MediaFileUploadError> { + if let Some(resample_fps) = form.maybe_resample_fps.as_ref() { + if **resample_fps > 24 { + return Err(MediaFileUploadError::BadInput("Resample FPS must be 24 or lower".to_string())); + } else if **resample_fps == 0 { + return Err(MediaFileUploadError::BadInput("Resample FPS must be greater than 0".to_string())); + } + } + + form.maybe_trim_start_millis.as_ref().zip(form.maybe_trim_end_millis.as_ref()) + .map(|(start, end)| { + if **start >= **end { + return Err(MediaFileUploadError::BadInput("Trim start must be less than trim end".to_string())); + } + Ok(()) + }) + .transpose()?; + + let is_intermediate_system_file = form.is_intermediate_system_file + .as_ref() + .map(|b| b.0) + .unwrap_or(false); + + if is_intermediate_system_file && form.maybe_prompt_token.is_some() { + return Err(MediaFileUploadError::BadInput( + "Cannot set `is_intermediate_system_file` to true if `maybe_prompt_token` is provided." + .to_string())); + } + + Ok(()) +} + +/// Map an API-key authentication failure onto this endpoint's error type. A genuine 401 stays a +/// 401; anything else (e.g. a DB error during lookup) becomes a 500. +fn map_api_key_auth_error(err: CommonWebError) -> MediaFileUploadError { + match err { + CommonWebError::NotAuthorized => { + MediaFileUploadError::NotAuthorizedVerbose("invalid or missing API key".to_string()) + } + _ => MediaFileUploadError::ServerError, + } +} diff --git a/crates/service/web/storyteller_web/src/http_server/routes/application_routes/omni_api_routes.rs b/crates/service/web/storyteller_web/src/http_server/routes/application_routes/omni_api_routes.rs index a889d7b66d..72a1f15082 100644 --- a/crates/service/web/storyteller_web/src/http_server/routes/application_routes/omni_api_routes.rs +++ b/crates/service/web/storyteller_web/src/http_server/routes/application_routes/omni_api_routes.rs @@ -5,6 +5,9 @@ use actix_web::{web, App, Error, HttpResponse}; use crate::http_server::endpoints::omni_api::generate::image::omni_api_image_generate_handler::omni_api_image_generate_handler; use crate::http_server::endpoints::omni_api::generate::video::omni_api_video_generate_handler::omni_api_video_generate_handler; +use crate::http_server::endpoints::omni_api::upload::omni_upload_audio_media_file_handler::omni_upload_audio_media_file_handler; +use crate::http_server::endpoints::omni_api::upload::omni_upload_image_media_file_handler::omni_upload_image_media_file_handler; +use crate::http_server::endpoints::omni_api::upload::omni_upload_video_media_file_handler::omni_upload_video_media_file_handler; /// API-key authenticated generation endpoints. These mirror `/v1/omni_gen/generate/*` but read the /// caller's identity from the `Authorization` header API key instead of a session cookie. The @@ -32,5 +35,19 @@ where .route(web::head().to(|| HttpResponse::Ok())) ) ) + .service(web::scope("/upload") + .service(web::resource("/audio") + .route(web::post().to(omni_upload_audio_media_file_handler)) + .route(web::head().to(|| HttpResponse::Ok())) + ) + .service(web::resource("/image") + .route(web::post().to(omni_upload_image_media_file_handler)) + .route(web::head().to(|| HttpResponse::Ok())) + ) + .service(web::resource("/video") + .route(web::post().to(omni_upload_video_media_file_handler)) + .route(web::head().to(|| HttpResponse::Ok())) + ) + ) ) } From 65e72f8c24f2bcd08aec451dd2eb608daa99625a Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Mon, 15 Jun 2026 23:06:05 -0400 Subject: [PATCH 06/53] omni api fixes (#1654) --- .../public/enums/src/common/platform_type.rs | 12 +++++++++++- .../image/omni_api_image_generate_handler.rs | 13 +++++-------- .../video/omni_api_video_generate_handler.rs | 17 +++++++---------- .../omni_upload_audio_media_file_handler.rs | 7 ++----- .../omni_upload_image_media_file_handler.rs | 7 ++----- .../omni_upload_video_media_file_handler.rs | 7 ++----- 6 files changed, 29 insertions(+), 34 deletions(-) diff --git a/crates/schema/public/enums/src/common/platform_type.rs b/crates/schema/public/enums/src/common/platform_type.rs index d1778f2c57..472e703062 100644 --- a/crates/schema/public/enums/src/common/platform_type.rs +++ b/crates/schema/public/enums/src/common/platform_type.rs @@ -26,6 +26,9 @@ pub enum PlatformType { /// Desktop app users DesktopApp, + /// Requests authenticated via an `Authorization` header API key (the Omni API surface). + ApiKey, + // ========== Other clients ========== Curl, @@ -42,6 +45,7 @@ impl PlatformType { match self { Self::Web => "web", Self::DesktopApp => "desktop_app", + Self::ApiKey => "api_key", Self::Curl => "curl", Self::PythonRequests => "python_requests", Self::Postman => "postman", @@ -52,6 +56,7 @@ impl PlatformType { match value { "web" => Ok(Self::Web), "desktop_app" => Ok(Self::DesktopApp), + "api_key" => Ok(Self::ApiKey), "curl" => Ok(Self::Curl), "python_requests" => Ok(Self::PythonRequests), "postman" => Ok(Self::Postman), @@ -64,6 +69,7 @@ impl PlatformType { BTreeSet::from([ Self::Web, Self::DesktopApp, + Self::ApiKey, Self::Curl, Self::PythonRequests, Self::Postman, @@ -84,6 +90,7 @@ mod tests { fn test_serialization() { assert_serialization(PlatformType::Web, "web"); assert_serialization(PlatformType::DesktopApp, "desktop_app"); + assert_serialization(PlatformType::ApiKey, "api_key"); assert_serialization(PlatformType::Curl, "curl"); assert_serialization(PlatformType::PythonRequests, "python_requests"); assert_serialization(PlatformType::Postman, "postman"); @@ -93,6 +100,7 @@ mod tests { fn to_str() { assert_eq!(PlatformType::Web.to_str(), "web"); assert_eq!(PlatformType::DesktopApp.to_str(), "desktop_app"); + assert_eq!(PlatformType::ApiKey.to_str(), "api_key"); assert_eq!(PlatformType::Curl.to_str(), "curl"); assert_eq!(PlatformType::PythonRequests.to_str(), "python_requests"); assert_eq!(PlatformType::Postman.to_str(), "postman"); @@ -102,6 +110,7 @@ mod tests { fn from_str() { assert_eq!(PlatformType::from_str("web").unwrap(), PlatformType::Web); assert_eq!(PlatformType::from_str("desktop_app").unwrap(), PlatformType::DesktopApp); + assert_eq!(PlatformType::from_str("api_key").unwrap(), PlatformType::ApiKey); assert_eq!(PlatformType::from_str("curl").unwrap(), PlatformType::Curl); assert_eq!(PlatformType::from_str("python_requests").unwrap(), PlatformType::PythonRequests); assert_eq!(PlatformType::from_str("postman").unwrap(), PlatformType::Postman); @@ -121,9 +130,10 @@ mod tests { #[test] fn all_variants() { let mut variants = PlatformType::all_variants(); - assert_eq!(variants.len(), 5); + assert_eq!(variants.len(), 6); assert_eq!(variants.pop_first(), Some(PlatformType::Web)); assert_eq!(variants.pop_first(), Some(PlatformType::DesktopApp)); + assert_eq!(variants.pop_first(), Some(PlatformType::ApiKey)); assert_eq!(variants.pop_first(), Some(PlatformType::Curl)); assert_eq!(variants.pop_first(), Some(PlatformType::PythonRequests)); assert_eq!(variants.pop_first(), Some(PlatformType::Postman)); diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/omni_api_image_generate_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/omni_api_image_generate_handler.rs index 1468e5aa9d..77811495f8 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/omni_api_image_generate_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/omni_api_image_generate_handler.rs @@ -14,6 +14,7 @@ use enums::by_table::prompts::prompt_type::PromptType; use enums::common::generation::common_generation_mode::CommonGenerationMode; use enums::common::generation::common_model_type::CommonModelType; use enums::common::generation_provider::GenerationProvider; +use enums::common::platform_type::PlatformType; use http_server_common::request::get_request_ip::get_request_ip; use mysql_queries::queries::debug_logs::insert_debug_log::{insert_debug_log, InsertDebugLogArgs}; use mysql_queries::queries::generic_inference::api_providers::seedance2pro::insert_generic_inference_job_for_seedance2pro_queue_with_apriori_job_token::KinoviVersion; @@ -35,7 +36,6 @@ use crate::http_server::endpoints::omni_api::generate::image::pipeline_v2::run_p use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; use crate::http_server::user_lookup::api_keys::require_api_key_user::require_api_key_user; use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; -use crate::http_server::web_utils::get_request_platform_type::get_request_platform_type; use crate::state::server_state::ServerState; use crate::util::lookup::lookup_media_files_as_cdn_url_list_and_map::lookup_media_files_as_cdn_url_list_and_map; @@ -78,10 +78,6 @@ pub async fn omni_api_image_generate_handler( let user_token = &api_session.user_token; - let maybe_avt_token = server_state - .avt_cookie_manager - .get_avt_token_from_request(&http_request); - // ==================== MODEL ACCESS CHECK ==================== // // API-key users have no per-user feature flags today. @@ -167,7 +163,8 @@ pub async fn omni_api_image_generate_handler( // ==================== WRITE RESULT ==================== // let ip_address = get_request_ip(&http_request); - let maybe_platform_type = get_request_platform_type(&http_request); + // Omni API requests are always API-key authenticated; hardcode the platform type. + let maybe_platform_type = Some(PlatformType::ApiKey); let mut transaction = mysql_connection .begin() @@ -262,7 +259,7 @@ pub async fn omni_api_image_generate_handler( apriori_job_token: &pipeline_result.apriori_job_token, idempotency_token: &idempotency_token, user_token, - maybe_avt_token: maybe_avt_token.as_ref(), + maybe_avt_token: None, // AVT tokens are web-session only; API-key callers have none. maybe_model_type: request.model.map(|v| v.to_common_model_type()), maybe_prompt_token: prompt_token.as_ref(), maybe_debug_log_event_token: Some(&debug_log_event_token), @@ -282,7 +279,7 @@ pub async fn omni_api_image_generate_handler( apriori_job_token: &pipeline_result.apriori_job_token, idempotency_token: &idempotency_token, user_token, - maybe_avt_token: maybe_avt_token.as_ref(), + maybe_avt_token: None, // AVT tokens are web-session only; API-key callers have none. maybe_model_type: request.model.map(|v| v.to_common_model_type()), maybe_prompt_token: prompt_token.as_ref(), maybe_debug_log_event_token: Some(&debug_log_event_token), diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/omni_api_video_generate_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/omni_api_video_generate_handler.rs index 62c26f18b2..1594305c47 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/omni_api_video_generate_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/omni_api_video_generate_handler.rs @@ -16,6 +16,7 @@ use enums::common::generation::common_generation_mode::CommonGenerationMode; use enums::common::generation::common_model_type::CommonModelType; use enums::common::generation::common_video_model::CommonVideoModel; use enums::common::generation_provider::GenerationProvider; +use enums::common::platform_type::PlatformType; use http_server_common::request::get_request_ip::get_request_ip; use mysql_queries::queries::debug_logs::insert_debug_log::{insert_debug_log, InsertDebugLogArgs}; use mysql_queries::queries::generic_inference::api_providers::seedance2pro::insert_generic_inference_job_for_seedance2pro_queue_with_apriori_job_token::KinoviVersion; @@ -44,7 +45,6 @@ use crate::http_server::endpoints::omni_api::shared_utils::video::validate_video use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; use crate::http_server::user_lookup::api_keys::require_api_key_user::require_api_key_user; use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; -use crate::http_server::web_utils::get_request_platform_type::get_request_platform_type; use crate::state::server_state::ServerState; use crate::util::lookup::lookup_media_files_as_cdn_url_list_and_map::lookup_media_files_as_cdn_url_list_and_map; @@ -91,10 +91,6 @@ pub async fn omni_api_video_generate_handler( let user_token = &api_session.user_token; - let maybe_avt_token = server_state - .avt_cookie_manager - .get_avt_token_from_request(&http_request); - // ==================== MODEL ACCESS CHECK ==================== // // API-key users have no per-user feature flags today. @@ -243,7 +239,8 @@ pub async fn omni_api_video_generate_handler( // ==================== WRITE RESULT ==================== // let ip_address = get_request_ip(&http_request); - let maybe_platform_type = get_request_platform_type(&http_request); + // Omni API requests are always API-key authenticated; hardcode the platform type. + let maybe_platform_type = Some(PlatformType::ApiKey); let mut transaction = mysql_connection.begin().await.map_err(|err| { error!("Error starting MySQL transaction: {:?}", err); @@ -344,7 +341,7 @@ pub async fn omni_api_video_generate_handler( apriori_job_token: &pipeline_result.billing.apriori_job_token, idempotency_token: &idempotency_token, user_token, - maybe_avt_token: maybe_avt_token.as_ref(), + maybe_avt_token: None, // AVT tokens are web-session only; API-key callers have none. maybe_model_type: request.model.map(|v| v.to_common_model_type()), maybe_prompt_token: prompt_token.as_ref(), maybe_debug_log_event_token: Some(&debug_log_event_token), @@ -368,7 +365,7 @@ pub async fn omni_api_video_generate_handler( apriori_job_token: &pipeline_result.billing.apriori_job_token, idempotency_token: &idempotency_token, user_token, - maybe_avt_token: maybe_avt_token.as_ref(), + maybe_avt_token: None, // AVT tokens are web-session only; API-key callers have none. maybe_model_type: request.model.map(|v| v.to_common_model_type()), maybe_prompt_token: prompt_token.as_ref(), maybe_debug_log_event_token: Some(&debug_log_event_token), @@ -398,7 +395,7 @@ pub async fn omni_api_video_generate_handler( apriori_job_token: &pipeline_result.billing.apriori_job_token, idempotency_token: &idempotency_token, user_token, - maybe_avt_token: maybe_avt_token.as_ref(), + maybe_avt_token: None, // AVT tokens are web-session only; API-key callers have none. maybe_model_type: request.model.map(|v| v.to_common_model_type()), maybe_prompt_token: prompt_token.as_ref(), maybe_debug_log_event_token: Some(&debug_log_event_token), @@ -422,7 +419,7 @@ pub async fn omni_api_video_generate_handler( apriori_job_token: &pipeline_result.billing.apriori_job_token, idempotency_token: &idempotency_token, user_token, - maybe_avt_token: maybe_avt_token.as_ref(), + maybe_avt_token: None, // AVT tokens are web-session only; API-key callers have none. maybe_model_type: request.model.map(|v| v.to_common_model_type()), maybe_prompt_token: prompt_token.as_ref(), maybe_debug_log_event_token: Some(&debug_log_event_token), diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/upload/omni_upload_audio_media_file_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/upload/omni_upload_audio_media_file_handler.rs index b1e444adae..d2055f62b7 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/upload/omni_upload_audio_media_file_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/upload/omni_upload_audio_media_file_handler.rs @@ -127,10 +127,6 @@ pub async fn omni_upload_audio_media_file_handler( let maybe_user_token = Some(&api_session.user_token); - let maybe_avt_token = server_state - .avt_cookie_manager - .get_avt_token_from_request(&http_request); - // ==================== RATE LIMIT ==================== // let rate_limiter = &server_state.redis_rate_limiters.file_upload_logged_in; @@ -283,7 +279,8 @@ pub async fn omni_upload_audio_media_file_handler( maybe_media_class: Some(MediaFileClass::Audio), media_file_type: MediaFileType::Audio, maybe_creator_user_token: maybe_user_token, - maybe_creator_anonymous_visitor_token: maybe_avt_token.as_ref(), + // NB: AVT (anonymous visitor) tokens are a web-session concept; API-key callers have none. + maybe_creator_anonymous_visitor_token: None, creator_ip_address: &ip_address, creator_set_visibility, upload_type, diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/upload/omni_upload_image_media_file_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/upload/omni_upload_image_media_file_handler.rs index e36f5b63a6..a7efced045 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/upload/omni_upload_image_media_file_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/upload/omni_upload_image_media_file_handler.rs @@ -138,10 +138,6 @@ pub async fn omni_upload_image_media_file_handler( let maybe_user_token = Some(&api_session.user_token); - let maybe_avt_token = server_state - .avt_cookie_manager - .get_avt_token_from_request(&http_request); - // ==================== HANDLE IDEMPOTENCY ==================== // let uuid_idempotency_token = form.uuid_idempotency_token.as_ref(); @@ -275,7 +271,8 @@ pub async fn omni_upload_image_media_file_handler( maybe_media_class: Some(MediaFileClass::Image), media_file_type, maybe_creator_user_token: maybe_user_token, - maybe_creator_anonymous_visitor_token: maybe_avt_token.as_ref(), + // NB: AVT (anonymous visitor) tokens are a web-session concept; API-key callers have none. + maybe_creator_anonymous_visitor_token: None, creator_ip_address: &ip_address, creator_set_visibility, upload_type, diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/upload/omni_upload_video_media_file_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/upload/omni_upload_video_media_file_handler.rs index 66b23f6b95..340a7f9010 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/upload/omni_upload_video_media_file_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/upload/omni_upload_video_media_file_handler.rs @@ -165,10 +165,6 @@ pub async fn omni_upload_video_media_file_handler( let maybe_user_token = Some(&api_session.user_token); - let maybe_avt_token = server_state - .avt_cookie_manager - .get_avt_token_from_request(&http_request); - // ==================== RATE LIMIT ==================== // let rate_limiter = &server_state.redis_rate_limiters.file_upload_logged_in; @@ -464,7 +460,8 @@ pub async fn omni_upload_video_media_file_handler( maybe_media_class: Some(MediaFileClass::Video), media_file_type: MediaFileType::Video, maybe_creator_user_token: maybe_user_token, - maybe_creator_anonymous_visitor_token: maybe_avt_token.as_ref(), + // NB: AVT (anonymous visitor) tokens are a web-session concept; API-key callers have none. + maybe_creator_anonymous_visitor_token: None, creator_ip_address: &ip_address, creator_set_visibility, upload_type, From 126f874c9076055f6a2fc4d123998a85b63904db Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Mon, 15 Jun 2026 23:25:58 -0400 Subject: [PATCH 07/53] api key feature flag (#1655) --- .../src/by_table/users/user_feature_flag.rs | 16 +++++++++++++++- .../endpoints/api_keys/create_api_key_handler.rs | 10 ++++++++++ .../session/lookup/user_session_feature_flags.rs | 4 ++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/crates/schema/public/enums/src/by_table/users/user_feature_flag.rs b/crates/schema/public/enums/src/by_table/users/user_feature_flag.rs index 9d6dd57084..b1e7cd5264 100644 --- a/crates/schema/public/enums/src/by_table/users/user_feature_flag.rs +++ b/crates/schema/public/enums/src/by_table/users/user_feature_flag.rs @@ -19,6 +19,7 @@ pub enum UserFeatureFlag { /// (For now, it's hidden until we get an NSFW filter.) ExploreMedia, + /// Access to studio features Studio, @@ -44,6 +45,10 @@ pub enum UserFeatureFlag { /// Access to the referrals program #[serde(rename = "referrals")] ReferralsProgram, + + /// Access to API key creation + #[serde(rename = "api_key")] + ApiKey, } // TODO(bt, 2022-12-21): This desperately needs MySQL integration tests! @@ -63,6 +68,7 @@ impl UserFeatureFlag { Self::HappyHorse => "hh", Self::HappyHorseRateLimit => "hh_rl", Self::ReferralsProgram => "referrals", + Self::ApiKey => "api_key", } } @@ -76,6 +82,7 @@ impl UserFeatureFlag { "hh" => Ok(Self::HappyHorse), "hh_rl" => Ok(Self::HappyHorseRateLimit), "referrals" => Ok(Self::ReferralsProgram), + "api_key" => Ok(Self::ApiKey), _ => Err(format!("invalid value: {:?}", value)), } } @@ -90,6 +97,7 @@ impl UserFeatureFlag { Self::HappyHorse => "Happy Horse (no Rate Limit)", Self::HappyHorseRateLimit => "Happy Horse (with Rate Limit)", Self::ReferralsProgram => "Referrals Program", + Self::ApiKey => "API Keys", } } @@ -103,6 +111,7 @@ impl UserFeatureFlag { Self::HappyHorse => "Access to Happy Horse (without a rate limit - dangerous)", Self::HappyHorseRateLimit => "Access to Happy Horse (with a rate limit)", Self::ReferralsProgram => "Access to the referrals program", + Self::ApiKey => "Access to API key creation", } } @@ -118,6 +127,7 @@ impl UserFeatureFlag { Self::HappyHorse, Self::HappyHorseRateLimit, Self::ReferralsProgram, + Self::ApiKey, ]) } } @@ -140,6 +150,7 @@ mod tests { assert_serialization(UserFeatureFlag::HappyHorse, "hh"); assert_serialization(UserFeatureFlag::HappyHorseRateLimit, "hh_rl"); assert_serialization(UserFeatureFlag::ReferralsProgram, "referrals"); + assert_serialization(UserFeatureFlag::ApiKey, "api_key"); } #[test] @@ -152,6 +163,7 @@ mod tests { assert_eq!(UserFeatureFlag::HappyHorse.to_str(), "hh"); assert_eq!(UserFeatureFlag::HappyHorseRateLimit.to_str(), "hh_rl"); assert_eq!(UserFeatureFlag::ReferralsProgram.to_str(), "referrals"); + assert_eq!(UserFeatureFlag::ApiKey.to_str(), "api_key"); } #[test] @@ -164,13 +176,14 @@ mod tests { assert_eq!(UserFeatureFlag::from_str("hh").unwrap(), UserFeatureFlag::HappyHorse); assert_eq!(UserFeatureFlag::from_str("hh_rl").unwrap(), UserFeatureFlag::HappyHorseRateLimit); assert_eq!(UserFeatureFlag::from_str("referrals").unwrap(), UserFeatureFlag::ReferralsProgram); + assert_eq!(UserFeatureFlag::from_str("api_key").unwrap(), UserFeatureFlag::ApiKey); assert!(UserFeatureFlag::from_str("foo").is_err()); } #[test] fn all_variants() { let mut variants = UserFeatureFlag::all_variants(); - assert_eq!(variants.len(), 8); + assert_eq!(variants.len(), 9); assert_eq!(variants.pop_first(), Some(UserFeatureFlag::ExploreMedia)); assert_eq!(variants.pop_first(), Some(UserFeatureFlag::Studio)); assert_eq!(variants.pop_first(), Some(UserFeatureFlag::Upload3d)); @@ -179,6 +192,7 @@ mod tests { assert_eq!(variants.pop_first(), Some(UserFeatureFlag::HappyHorse)); assert_eq!(variants.pop_first(), Some(UserFeatureFlag::HappyHorseRateLimit)); assert_eq!(variants.pop_first(), Some(UserFeatureFlag::ReferralsProgram)); + assert_eq!(variants.pop_first(), Some(UserFeatureFlag::ApiKey)); assert_eq!(variants.pop_first(), None); } } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/create_api_key_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/create_api_key_handler.rs index 191845436b..f08b8d7190 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/create_api_key_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/create_api_key_handler.rs @@ -10,6 +10,7 @@ use http_server_common::request::get_request_ip::get_request_ip; use mysql_queries::queries::api_keys::insert_api_key::{insert_api_key, InsertApiKeyArgs}; use crate::http_server::common_responses::common_web_error::CommonWebError; +use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; use crate::http_server::web_utils::user_session::require_user_session::require_user_session; use crate::state::server_state::ServerState; use crate::util::generate_api_key::generate_api_key; @@ -29,6 +30,7 @@ const MAX_DESCRIPTION_LEN: usize = 512; (status = 200, body = CreateApiKeySuccessResponse), (status = 400, body = CommonWebError), (status = 401, body = CommonWebError), + (status = 403, body = CommonWebError), (status = 500, body = CommonWebError), ), )] @@ -44,6 +46,14 @@ pub async fn create_api_key_handler( let user_session = require_user_session(&http_request, &server_state.session_checker, &mut *conn).await?; + // API key creation is gated behind the `api_key` feature flag. + let feature_flags = UserSessionFeatureFlags::new(user_session.maybe_feature_flags.as_deref()); + + if !feature_flags.can_create_api_key() { + warn!("User {:?} attempted to create an API key without the api_key feature flag", user_session.user_token.as_str()); + return Err(CommonWebError::Forbidden); + } + let name = request.name.trim().to_string(); let maybe_description = request.maybe_description.as_ref().map(|s| s.trim().to_string()); diff --git a/crates/service/web/storyteller_web/src/http_server/session/lookup/user_session_feature_flags.rs b/crates/service/web/storyteller_web/src/http_server/session/lookup/user_session_feature_flags.rs index ace366d2dc..0f2e711b20 100644 --- a/crates/service/web/storyteller_web/src/http_server/session/lookup/user_session_feature_flags.rs +++ b/crates/service/web/storyteller_web/src/http_server/session/lookup/user_session_feature_flags.rs @@ -101,6 +101,10 @@ impl UserSessionFeatureFlags { pub fn can_use_referrals_program(&self) -> bool { self.feature_flags.contains(&UserFeatureFlag::ReferralsProgram) } + + pub fn can_create_api_key(&self) -> bool { + self.feature_flags.contains(&UserFeatureFlag::ApiKey) + } } #[cfg(test)] From 29a127aac0b07f86bbaec0eef3d64fe6052368ba Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Tue, 16 Jun 2026 00:30:06 -0400 Subject: [PATCH 08/53] jsonify actix handlers (#1656) --- .../analytics/log_browser_session_handler.rs | 9 ++------- .../beta_keys/create_beta_keys_handler.rs | 11 +++-------- .../edit_beta_key_distributed_flag_handler.rs | 11 +++-------- .../beta_keys/edit_beta_key_note_handler.rs | 11 +++-------- .../beta_keys/list_beta_keys_handler.rs | 11 +++-------- .../beta_keys/redeem_beta_key_handler.rs | 11 +++-------- .../comments/create_comment_handler.rs | 9 ++------- .../comments/delete_comment_handler.rs | 5 ++--- .../comments/list_comments_handler.rs | 9 ++------- .../download_job/enqueue_generic_download.rs | 9 ++------- .../get_generic_upload_job_status.rs | 9 ++------- .../create_featured_item_handler.rs | 11 +++-------- .../delete_featured_item_handler.rs | 7 +++---- .../admin/kill_inference_jobs_handler.rs | 11 +++-------- .../delete/terminate_inference_job_handler.rs | 14 +++----------- .../change_media_file_visibility_handler.rs | 12 ++++-------- .../edit/rename_media_file_handler.rs | 11 +++-------- .../edit/set_media_file_cover_image_handler.rs | 11 +++-------- .../edit/update_media_file_handler.rs | 11 +++-------- .../info/moderator_token_info_handler.rs | 11 +++-------- .../endpoints/moderation/ip_bans/add_ip_ban.rs | 8 ++++---- .../moderation/ip_bans/delete_ip_ban.rs | 8 ++++---- .../endpoints/moderation/ip_bans/get_ip_ban.rs | 11 +++-------- .../moderation/ip_bans/list_ip_bans.rs | 11 +++-------- .../jobs/get_tts_inference_queue_count.rs | 11 +++-------- .../jobs/get_w2l_inference_queue_count.rs | 9 ++------- .../moderation/jobs/kill_tts_inference_jobs.rs | 11 +++-------- ...oderator_edit_user_feature_flags_handler.rs | 9 ++++----- .../subscriptions/unsubscribe_reason.rs | 11 +++-------- .../trending/list_trending_tts_models.rs | 11 +++-------- .../endpoints/tts/delete_tts_model.rs | 8 ++++---- .../endpoints/tts/delete_tts_result.rs | 8 ++++---- .../endpoints/tts/edit_tts_model.rs | 8 ++++---- .../endpoints/tts/edit_tts_result.rs | 8 ++++---- .../endpoints/tts/enqueue_upload_tts_model.rs | 17 +++++------------ .../tts/get_tts_inference_job_status.rs | 18 ++++-------------- .../http_server/endpoints/tts/get_tts_model.rs | 11 +++-------- .../endpoints/tts/get_tts_model_use_count.rs | 15 ++++----------- .../endpoints/tts/get_tts_result.rs | 15 ++++----------- .../tts/get_tts_upload_model_job_status.rs | 15 ++++----------- .../endpoints/tts/search_tts_models_handler.rs | 15 ++++----------- .../create_user_bookmark_handler.rs | 9 ++------- .../delete_user_bookmark_handler.rs | 5 ++--- .../list_user_bookmarks_for_entity_handler.rs | 9 ++------- .../list_user_bookmarks_for_session_handler.rs | 9 ++------- .../user_ratings/get_user_rating_handler.rs | 13 +++---------- .../user_ratings/set_user_rating_handler.rs | 13 +++---------- .../endpoints/users/edit_profile_handler.rs | 11 +++-------- .../check_if_voice_clone_request_submitted.rs | 11 +++-------- .../create_voice_clone_request.rs | 11 +++-------- .../inference/enqueue_tts_request.rs | 11 ++++------- .../voice_dataset_samples/delete_sample.rs | 10 ++++------ .../list_samples_by_dataset.rs | 13 +++---------- .../voice_datasets/create_dataset.rs | 14 +++----------- .../voice_datasets/delete_dataset.rs | 11 ++++------- .../voice_datasets/get_dataset.rs | 13 +++---------- .../voice_datasets/list_datasets_by_session.rs | 11 +++-------- .../voice_datasets/list_datasets_by_user.rs | 11 +++-------- .../voice_datasets/update_dataset.rs | 11 ++++------- .../voice_designer/voices/create_voice.rs | 13 +++---------- .../voice_designer/voices/delete_voice.rs | 11 ++++------- .../voice_designer/voices/get_voice.rs | 13 +++---------- .../voices/list_available_voices.rs | 13 +++---------- .../voices/list_voices_by_session.rs | 11 +++-------- .../voices/list_voices_by_user.rs | 11 +++-------- .../voice_designer/voices/update_voice.rs | 11 ++++------- .../weights/delete/delete_weight_handler.rs | 11 +++-------- .../weights/get/get_weight_handler.rs | 14 +++----------- .../list/list_available_weights_handler.rs | 9 +++------ .../list/list_featured_weights_handler.rs | 11 +++-------- .../list/list_pinned_weights_handler.rs | 11 +++-------- .../list/list_weights_by_user_handler.rs | 14 +++----------- .../set_model_weight_cover_image_handler.rs | 11 +++-------- .../weights/update/update_weight_handler.rs | 12 ++++-------- 74 files changed, 233 insertions(+), 581 deletions(-) diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/analytics/log_browser_session_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/analytics/log_browser_session_handler.rs index f7e6d91307..b6187ff779 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/analytics/log_browser_session_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/analytics/log_browser_session_handler.rs @@ -89,7 +89,7 @@ pub async fn log_browser_session_handler( http_request: HttpRequest, request: web::Json, server_state: web::Data>, -) -> Result +) -> Result, LogBrowserSessionError> { let mut mysql_connection = server_state.mysql_pool .acquire() @@ -131,11 +131,6 @@ pub async fn log_browser_session_handler( log_token: token, }; - let body = serde_json::to_string(&response) - .map_err(|_e| LogBrowserSessionError::ServerError)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/create_beta_keys_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/create_beta_keys_handler.rs index c80cf8bfc5..40ddfaf5ed 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/create_beta_keys_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/create_beta_keys_handler.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::warn; use utoipa::ToSchema; @@ -58,7 +58,7 @@ pub async fn create_beta_keys_handler( http_request: HttpRequest, request: web::Json, server_state: web::Data>, -) -> Result +) -> Result, CommonWebError> { let user_session = require_moderator(&http_request, &server_state.session_checker, &server_state.mysql_pool).await?; @@ -106,11 +106,6 @@ pub async fn create_beta_keys_handler( beta_keys, }; - let body = serde_json::to_string(&response) - .map_err(CommonWebError::from_error)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/edit_beta_key_distributed_flag_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/edit_beta_key_distributed_flag_handler.rs index eae701228a..e5253b9a8f 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/edit_beta_key_distributed_flag_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/edit_beta_key_distributed_flag_handler.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; use actix_web::web::Path; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::warn; use utoipa::ToSchema; @@ -56,7 +56,7 @@ pub async fn edit_beta_key_distributed_flag_handler( request: web::Json, path: Path, server_state: web::Data>, -) -> Result +) -> Result, CommonWebError> { let user_session = require_moderator(&http_request, &server_state.session_checker, &server_state.mysql_pool).await?; @@ -71,10 +71,5 @@ pub async fn edit_beta_key_distributed_flag_handler( success: true, }; - let body = serde_json::to_string(&response) - .map_err(CommonWebError::from_error)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/edit_beta_key_note_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/edit_beta_key_note_handler.rs index 378d2daa12..67525df93d 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/edit_beta_key_note_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/edit_beta_key_note_handler.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; use actix_web::web::Path; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::warn; use utoipa::ToSchema; @@ -55,7 +55,7 @@ pub async fn edit_beta_key_note_handler( request: web::Json, path: Path, server_state: web::Data>, -) -> Result +) -> Result, CommonWebError> { let user_session = require_moderator(&http_request, &server_state.session_checker, &server_state.mysql_pool).await?; @@ -75,10 +75,5 @@ pub async fn edit_beta_key_note_handler( success: true, }; - let body = serde_json::to_string(&response) - .map_err(CommonWebError::from_error)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/list_beta_keys_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/list_beta_keys_handler.rs index 89cb21b91f..9d4de4c760 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/list_beta_keys_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/list_beta_keys_handler.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; use actix_web::web::Query; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use chrono::{DateTime, Utc}; use log::warn; use utoipa::{IntoParams, ToSchema}; @@ -107,7 +107,7 @@ pub async fn list_beta_keys_handler( http_request: HttpRequest, query: Query, server_state: web::Data> -) -> Result { +) -> Result, CommonWebError> { let user_session = require_user_session(&http_request, &server_state.session_checker, &server_state.mysql_pool) .await?; @@ -212,10 +212,5 @@ pub async fn list_beta_keys_handler( } }; - let body = serde_json::to_string(&response) - .map_err(CommonWebError::from_error)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/redeem_beta_key_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/redeem_beta_key_handler.rs index f23c536825..e23361b3ea 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/redeem_beta_key_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/redeem_beta_key_handler.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::warn; use utoipa::ToSchema; @@ -50,7 +50,7 @@ pub async fn redeem_beta_key_handler( http_request: HttpRequest, request: web::Json, server_state: web::Data>, -) -> Result +) -> Result, CommonWebError> { let user_session = require_user_session(&http_request, &server_state.session_checker, &server_state.mysql_pool).await?; @@ -90,12 +90,7 @@ pub async fn redeem_beta_key_handler( success: true, }; - // `?` via the `From` impl on CommonWebError. - let body = serde_json::to_string(&response)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } async fn enroll_in_studio( diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/comments/create_comment_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/comments/create_comment_handler.rs index 33cdf9b035..1085449ca9 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/comments/create_comment_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/comments/create_comment_handler.rs @@ -97,7 +97,7 @@ pub async fn create_comment_handler( http_request: HttpRequest, request: web::Json, server_state: web::Data>, -) -> Result +) -> Result, CreateCommentError> { // NB(bt,2023-12-14): Kasisnu found that we're getting entity type mismatches in production. Apart from // querying the database for entity existence, this is the next best way to prevent incorrect comment @@ -188,10 +188,5 @@ pub async fn create_comment_handler( comment_token, }; - let body = serde_json::to_string(&response) - .map_err(|_e| CreateCommentError::ServerError)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/comments/delete_comment_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/comments/delete_comment_handler.rs index 931c5d86eb..ac7fef6681 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/comments/delete_comment_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/comments/delete_comment_handler.rs @@ -19,7 +19,6 @@ use tokens::tokens::comments::CommentToken; use artcraft_api_defs::common::responses::simple_generic_json_success::SimpleGenericJsonSuccess; use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; -use crate::http_server::web_utils::response_success_helpers::simple_json_success; use crate::state::server_state::ServerState; /// For the URL PathInfo @@ -92,7 +91,7 @@ pub async fn delete_comment_handler( path: Path, request: web::Json, server_state: web::Data> -) -> Result { +) -> Result, DeleteCommentError> { let mut mysql_connection = server_state.mysql_pool .acquire() .await @@ -166,5 +165,5 @@ pub async fn delete_comment_handler( } }; - Ok(simple_json_success()) + Ok(web::Json(SimpleGenericJsonSuccess { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/comments/list_comments_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/comments/list_comments_handler.rs index 0488150846..4b6b4632f7 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/comments/list_comments_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/comments/list_comments_handler.rs @@ -129,7 +129,7 @@ pub async fn list_comments_handler( _http_request: HttpRequest, path: Path, server_state: web::Data> -) -> Result +) -> Result, ListCommentsError> { let entity_token = CommentEntityToken::from_entity_type_and_token( path.entity_type, &path.entity_token); @@ -174,10 +174,5 @@ pub async fn list_comments_handler( .collect(), }; - let body = serde_json::to_string(&response) - .map_err(|_e| ListCommentsError::ServerError)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/download_job/enqueue_generic_download.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/download_job/enqueue_generic_download.rs index 87404114df..1e8df920e7 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/download_job/enqueue_generic_download.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/download_job/enqueue_generic_download.rs @@ -70,7 +70,7 @@ impl fmt::Display for EnqueueGenericDownloadError { pub async fn enqueue_generic_download_handler( http_request: HttpRequest, request: web::Json, - server_state: web::Data>) -> Result + server_state: web::Data>) -> Result, EnqueueGenericDownloadError> { if let Err(_err) = server_state.redis_rate_limiters.model_upload.rate_limit_request(&http_request).await { return Err(EnqueueGenericDownloadError::RateLimited); @@ -150,10 +150,5 @@ pub async fn enqueue_generic_download_handler( job_token: job_token.to_string(), }; - let body = serde_json::to_string(&response) - .map_err(|e| EnqueueGenericDownloadError::ServerError)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/download_job/get_generic_upload_job_status.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/download_job/get_generic_upload_job_status.rs index f3fb03978d..ab8b2b1a10 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/download_job/get_generic_upload_job_status.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/download_job/get_generic_upload_job_status.rs @@ -87,7 +87,7 @@ impl fmt::Display for GetGenericDownloadJobStatusError { pub async fn get_generic_download_job_status_handler( http_request: HttpRequest, path: Path, - server_state: web::Data>) -> Result + server_state: web::Data>) -> Result, GetGenericDownloadJobStatusError> { let maybe_job_status = get_generic_download_job_status( &path.token, @@ -142,10 +142,5 @@ pub async fn get_generic_download_job_status_handler( state: model_for_response, }; - let body = serde_json::to_string(&response) - .map_err(|e| GetGenericDownloadJobStatusError::ServerError)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/featured_items/create_featured_item_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/featured_items/create_featured_item_handler.rs index f631e36c77..8012b82527 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/featured_items/create_featured_item_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/featured_items/create_featured_item_handler.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::{error, warn}; use sqlx::Acquire; use utoipa::ToSchema; @@ -52,7 +52,7 @@ pub async fn create_featured_item_handler( http_request: HttpRequest, request: web::Json, server_state: web::Data>, -) -> Result +) -> Result, CommonWebError> { // NB(bt,2023-12-14): Kasisnu found that we're getting entity type mismatches in production. Apart from // querying the database for entity existence, this is the next best way to prevent incorrect comment @@ -163,10 +163,5 @@ pub async fn create_featured_item_handler( success: true, }; - let body = serde_json::to_string(&response) - .map_err(CommonWebError::from_error)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/featured_items/delete_featured_item_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/featured_items/delete_featured_item_handler.rs index 4ed891afb0..93e998e8b5 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/featured_items/delete_featured_item_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/featured_items/delete_featured_item_handler.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::{error, warn}; use sqlx::Acquire; use utoipa::ToSchema; @@ -22,7 +22,6 @@ use tokens::tokens::users::UserToken; use artcraft_api_defs::common::responses::simple_generic_json_success::SimpleGenericJsonSuccess; use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::response_success_helpers::simple_json_success; use crate::state::server_state::ServerState; #[derive(Deserialize, ToSchema)] @@ -49,7 +48,7 @@ pub async fn delete_featured_item_handler( http_request: HttpRequest, request: web::Json, server_state: web::Data> -) -> Result { +) -> Result, CommonWebError> { // NB(bt,2023-12-14): Kasisnu found that we're getting entity type mismatches in production. Apart from // querying the database for entity existence, this is the next best way to prevent incorrect comment @@ -138,5 +137,5 @@ pub async fn delete_featured_item_handler( } }; - Ok(simple_json_success()) + Ok(web::Json(SimpleGenericJsonSuccess { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/inference_job/admin/kill_inference_jobs_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/inference_job/admin/kill_inference_jobs_handler.rs index fe57f09637..ce33ea2646 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/inference_job/admin/kill_inference_jobs_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/inference_job/admin/kill_inference_jobs_handler.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::warn; use enums::by_table::generic_inference_jobs::inference_category::InferenceCategory; @@ -53,7 +53,7 @@ pub struct KillInferenceJobsResponse { pub async fn kill_generic_inference_jobs_handler( http_request: HttpRequest, request: web::Json, - server_state: web::Data>) -> Result + server_state: web::Data>) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -106,10 +106,5 @@ pub async fn kill_generic_inference_jobs_handler( success: true, }; - let body = serde_json::to_string(&response) - .map_err(CommonWebError::from_error)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/inference_job/delete/terminate_inference_job_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/inference_job/delete/terminate_inference_job_handler.rs index 0e230ebca9..e04f572f44 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/inference_job/delete/terminate_inference_job_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/inference_job/delete/terminate_inference_job_handler.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; use actix_web::web::Path; -use actix_web::{web, HttpMessage, HttpRequest, HttpResponse}; +use actix_web::{web, HttpMessage, HttpRequest}; use log::{error, warn}; use utoipa::ToSchema; @@ -47,7 +47,7 @@ pub struct TerminateInferenceJobSuccessResponse { pub async fn terminate_inference_job_handler( http_request: HttpRequest, path: Path, - server_state: web::Data>) -> Result + server_state: web::Data>) -> Result, CommonWebError> { let mut mysql_connection = server_state.mysql_pool.acquire() .await @@ -105,13 +105,5 @@ pub async fn terminate_inference_job_handler( success: true, }; - let body = serde_json::to_string(&response) - .map_err(|e| { - error!("error returning response: {:?}", e); - CommonWebError::from_error(e) - })?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/change_media_file_visibility_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/change_media_file_visibility_handler.rs index 1f9dfd3f92..5ad95dc0c4 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/change_media_file_visibility_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/change_media_file_visibility_handler.rs @@ -1,17 +1,13 @@ -use std::fmt; use std::sync::Arc; -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; use actix_web::web::Path; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::warn; use utoipa::ToSchema; use enums::common::visibility::Visibility; use http_server_common::request::get_request_ip::get_request_ip; -use http_server_common::response::response_success_helpers::{simple_json_success, SimpleGenericJsonSuccess}; -use http_server_common::response::serialize_as_json_error::serialize_as_json_error; +use http_server_common::response::response_success_helpers::SimpleGenericJsonSuccess; use mysql_queries::queries::media_files::edit::update_media_file_visibility::{update_media_file_visibility, UpdateMediaFileArgs}; use mysql_queries::queries::media_files::get::get_media_file::get_media_file; @@ -48,7 +44,7 @@ pub async fn change_media_file_visibility_handler( http_request: HttpRequest, path: Path, request: web::Json, - server_state: web::Data>) -> Result + server_state: web::Data>) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -127,5 +123,5 @@ pub async fn change_media_file_visibility_handler( } }; - Ok(simple_json_success()) + Ok(web::Json(SimpleGenericJsonSuccess { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/rename_media_file_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/rename_media_file_handler.rs index 422d7eef62..fab1485dda 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/rename_media_file_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/rename_media_file_handler.rs @@ -1,19 +1,14 @@ -use std::fmt; use std::sync::Arc; -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; use actix_web::web::Path; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::warn; use utoipa::ToSchema; use crate::http_server::common_requests::media_file_token_path_info::MediaFileTokenPathInfo; use crate::http_server::common_responses::common_web_error::CommonWebError; use artcraft_api_defs::common::responses::simple_generic_json_success::SimpleGenericJsonSuccess; -use crate::http_server::web_utils::response_success_helpers::simple_json_success; use crate::state::server_state::ServerState; -use http_server_common::response::serialize_as_json_error::serialize_as_json_error; use mysql_queries::queries::media_files::edit::rename_media_file::rename_media_file; use mysql_queries::queries::media_files::get::get_media_file::get_media_file; @@ -49,7 +44,7 @@ pub async fn rename_media_file_handler( path: Path, request: web::Json, server_state: web::Data> -) -> Result{ +) -> Result, CommonWebError>{ let maybe_user_session = server_state .session_checker .maybe_get_user_session(&http_request, &server_state.mysql_pool) @@ -104,5 +99,5 @@ pub async fn rename_media_file_handler( CommonWebError::from_anyhow_error(err) })?; - Ok(simple_json_success()) + Ok(web::Json(SimpleGenericJsonSuccess { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/set_media_file_cover_image_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/set_media_file_cover_image_handler.rs index b2101d9196..c8445edc77 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/set_media_file_cover_image_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/set_media_file_cover_image_handler.rs @@ -1,15 +1,11 @@ -use std::fmt; use std::sync::Arc; -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; use actix_web::web::Path; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::{info, warn}; use utoipa::ToSchema; use enums::by_table::media_files::media_file_type::MediaFileType; -use http_server_common::response::serialize_as_json_error::serialize_as_json_error; use mysql_queries::queries::media_files::edit::set_media_file_cover_image::{set_media_file_cover_image, UpdateArgs}; use mysql_queries::queries::media_files::get::get_media_file::get_media_file; use tokens::tokens::media_files::MediaFileToken; @@ -17,7 +13,6 @@ use tokens::tokens::media_files::MediaFileToken; use crate::http_server::common_requests::media_file_token_path_info::MediaFileTokenPathInfo; use crate::http_server::common_responses::common_web_error::CommonWebError; use artcraft_api_defs::common::responses::simple_generic_json_success::SimpleGenericJsonSuccess; -use crate::http_server::web_utils::response_success_helpers::simple_json_success; use crate::state::server_state::ServerState; #[derive(Deserialize, ToSchema)] @@ -55,7 +50,7 @@ pub async fn set_media_file_cover_image_handler( path: Path, request: web::Json, server_state: web::Data> -) -> Result{ +) -> Result, CommonWebError>{ let maybe_user_session = server_state .session_checker .maybe_get_user_session(&http_request, &server_state.mysql_pool) @@ -157,5 +152,5 @@ pub async fn set_media_file_cover_image_handler( } }; - Ok(simple_json_success()) + Ok(web::Json(SimpleGenericJsonSuccess { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/update_media_file_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/update_media_file_handler.rs index affd7a55b2..e183a44d37 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/update_media_file_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/update_media_file_handler.rs @@ -1,16 +1,11 @@ -use std::fmt; use std::sync::Arc; -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; use actix_web::web::Path; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::warn; use enums::common::visibility::Visibility; use http_server_common::request::get_request_ip::get_request_ip; -use http_server_common::response::response_success_helpers::simple_json_success; -use http_server_common::response::serialize_as_json_error::serialize_as_json_error; use mysql_queries::queries::media_files::edit::update_media_file_visibility::{update_media_file_visibility, UpdateMediaFileArgs}; use mysql_queries::queries::media_files::get::get_media_file::get_media_file; use tokens::tokens::media_files::MediaFileToken; @@ -42,7 +37,7 @@ pub async fn update_media_file_handler( http_request: HttpRequest, path: Path, request: web::Json, - server_state: web::Data>) -> Result + server_state: web::Data>) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -121,5 +116,5 @@ pub async fn update_media_file_handler( } }; - Ok(simple_json_success()) + Ok(web::Json(UpdateMediaFileResponse { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/info/moderator_token_info_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/info/moderator_token_info_handler.rs index ec7ab084bc..8ac66ae171 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/info/moderator_token_info_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/info/moderator_token_info_handler.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; use actix_web::web::Path; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::warn; use serde::Serialize; use sqlx::MySqlPool; @@ -58,7 +58,7 @@ pub async fn moderator_get_token_info_handler( path: Path, http_request: HttpRequest, server_state: web::Data> -) -> Result { +) -> Result, CommonWebError> { let user_session = require_moderator(&http_request, &server_state.session_checker, &server_state.mysql_pool).await?; @@ -75,12 +75,7 @@ pub async fn moderator_get_token_info_handler( maybe_payload: maybe_result, }; - let body = serde_json::to_string(&response) - .map_err(CommonWebError::from_error)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } const LEGACY_USER_TOKEN_PREFIX : &str = "U:"; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/add_ip_ban.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/add_ip_ban.rs index aaf371dce9..4f087258d5 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/add_ip_ban.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/add_ip_ban.rs @@ -3,15 +3,15 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::{info, warn}; +use artcraft_api_defs::common::responses::simple_generic_json_success::SimpleGenericJsonSuccess; use mysql_queries::queries::ip_bans::upsert_ip_ban::{upsert_ip_ban, UpsertIpBanArgs}; use user_input_common::validate_user_provided_ip_address::validate_user_provided_ip_address; use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::response_success_helpers::simple_json_success; use crate::state::server_state::ServerState; #[derive(Deserialize)] @@ -25,7 +25,7 @@ pub async fn add_ip_ban_handler( http_request: HttpRequest, request: web::Json, server_state: web::Data> -) -> Result { +) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -70,5 +70,5 @@ pub async fn add_ip_ban_handler( CommonWebError::from_anyhow_error(err) })?; - Ok(simple_json_success()) + Ok(web::Json(SimpleGenericJsonSuccess { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/delete_ip_ban.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/delete_ip_ban.rs index 5cbe146b6a..47a46afc5a 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/delete_ip_ban.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/delete_ip_ban.rs @@ -3,15 +3,15 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; use actix_web::web::Path; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use derive_more::Display; use log::{info, warn}; +use artcraft_api_defs::common::responses::simple_generic_json_success::SimpleGenericJsonSuccess; use mysql_queries::queries::ip_bans::toggle_ip_ban::{toggle_ip_ban, IpBanToggleState}; use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::response_success_helpers::simple_json_success; use crate::state::server_state::ServerState; /// For the URL PathInfo @@ -29,7 +29,7 @@ pub async fn delete_ip_ban_handler( path: Path, request: web::Json, server_state: web::Data> -) -> Result { +) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -77,5 +77,5 @@ pub async fn delete_ip_ban_handler( } }; - Ok(simple_json_success()) + Ok(web::Json(SimpleGenericJsonSuccess { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/get_ip_ban.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/get_ip_ban.rs index 260edd35b3..521db56d99 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/get_ip_ban.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/get_ip_ban.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; use actix_web::web::Path; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use chrono::{DateTime, Utc}; use derive_more::Display; use log::{error, warn}; @@ -42,7 +42,7 @@ pub async fn get_ip_ban_handler( http_request: HttpRequest, path: Path, server_state: web::Data> -) -> Result { +) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -97,10 +97,5 @@ pub async fn get_ip_ban_handler( ip_address_ban: result, }; - let body = serde_json::to_string(&response) - .map_err(CommonWebError::from_error)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/list_ip_bans.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/list_ip_bans.rs index 29fa9bccf6..c63c3b4505 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/list_ip_bans.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/list_ip_bans.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use chrono::{DateTime, Utc}; use log::{error, warn}; @@ -34,7 +34,7 @@ pub struct IpBanRecordForList { pub async fn list_ip_bans_handler( http_request: HttpRequest, server_state: web::Data> -) -> Result { +) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -85,10 +85,5 @@ pub async fn list_ip_bans_handler( ip_address_bans: results, }; - let body = serde_json::to_string(&response) - .map_err(CommonWebError::from_error)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/get_tts_inference_queue_count.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/get_tts_inference_queue_count.rs index 753082dc5c..dc4d39df3f 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/get_tts_inference_queue_count.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/get_tts_inference_queue_count.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::warn; use mysql_queries::queries::tts::tts_inference_jobs::get_pending_tts_inference_job_detailed_stats::{get_pending_tts_inference_job_detailed_stats, PendingCountResult}; @@ -34,7 +34,7 @@ pub struct GetTtsInferenceQueueCountResponse { pub async fn get_tts_inference_queue_count_handler( http_request: HttpRequest, server_state: web::Data> -) -> Result { +) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -85,10 +85,5 @@ pub async fn get_tts_inference_queue_count_handler( attempt_failed_count: result.attempt_failed_count, }; - let body = serde_json::to_string(&response) - .map_err(CommonWebError::from_error)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/get_w2l_inference_queue_count.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/get_w2l_inference_queue_count.rs index b76efeaf17..149994eeda 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/get_w2l_inference_queue_count.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/get_w2l_inference_queue_count.rs @@ -47,7 +47,7 @@ impl fmt::Display for GetW2lInferenceQueueCountError { pub async fn get_w2l_inference_queue_count_handler( http_request: HttpRequest, server_state: web::Data> -) -> Result { +) -> Result, GetW2lInferenceQueueCountError> { let maybe_user_session = server_state .session_checker @@ -92,10 +92,5 @@ pub async fn get_w2l_inference_queue_count_handler( seconds_since_first: result.seconds_since_first, }; - let body = serde_json::to_string(&response) - .map_err(|e| GetW2lInferenceQueueCountError::ServerError)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/kill_tts_inference_jobs.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/kill_tts_inference_jobs.rs index bca96c4f9c..ab81e8ab06 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/kill_tts_inference_jobs.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/kill_tts_inference_jobs.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::{error, warn}; use http_server_common::response::serialize_as_json_error::serialize_as_json_error; @@ -46,7 +46,7 @@ pub struct KillTtsInferenceJobsResponse { pub async fn kill_tts_inference_jobs_handler( http_request: HttpRequest, request: web::Json, - server_state: web::Data>) -> Result + server_state: web::Data>) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -88,10 +88,5 @@ pub async fn kill_tts_inference_jobs_handler( success: true, }; - let body = serde_json::to_string(&response) - .map_err(CommonWebError::from_error)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_feature_flags/moderator_edit_user_feature_flags_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_feature_flags/moderator_edit_user_feature_flags_handler.rs index 6f5ee84308..e22e076df1 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_feature_flags/moderator_edit_user_feature_flags_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_feature_flags/moderator_edit_user_feature_flags_handler.rs @@ -6,8 +6,8 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; -use actix_web::web::{Data, Json, Path}; -use actix_web::{HttpRequest, HttpResponse}; +use actix_web::web::{self, Data, Json, Path}; +use actix_web::HttpRequest; use log::warn; use redis::{Client, Commands}; use sqlx::Acquire; @@ -26,7 +26,6 @@ use tokens::tokens::users::UserToken; use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; -use crate::http_server::web_utils::response_success_helpers::simple_json_success; use crate::http_server::web_utils::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; use artcraft_api_defs::common::responses::simple_generic_json_success::SimpleGenericJsonSuccess; @@ -85,7 +84,7 @@ pub async fn moderator_edit_user_feature_flags_handler( request: Json, server_state: Data>, redis_pool: Data>, -) -> Result { +) -> Result, CommonWebError> { let user_session = require_moderator(&http_request, &server_state.session_checker, &server_state.mysql_pool).await?; @@ -190,5 +189,5 @@ pub async fn moderator_edit_user_feature_flags_handler( let _r: Result, _> = redis.del(&cache_key); } - Ok(simple_json_success()) + Ok(web::Json(SimpleGenericJsonSuccess { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/subscriptions/unsubscribe_reason.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/subscriptions/unsubscribe_reason.rs index 91649d02bd..d33a6c39b6 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/subscriptions/unsubscribe_reason.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/subscriptions/unsubscribe_reason.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use actix_web::http::StatusCode; -use actix_web::{web, HttpRequest, HttpResponse, ResponseError}; +use actix_web::{web, HttpRequest}; use log::{error, info}; use http_server_common::request::get_request_ip::get_request_ip; @@ -32,7 +32,7 @@ pub struct SetUnsubscribeReasonResponse { pub async fn set_unsubscribe_reason_handler( http_request: HttpRequest, request: web::Json, - server_state: web::Data>) -> Result + server_state: web::Data>) -> Result, CommonWebError> { let mut mysql_connection = server_state.mysql_pool.acquire() .await @@ -73,10 +73,5 @@ pub async fn set_unsubscribe_reason_handler( success: true, }; - let body = serde_json::to_string(&response) - .map_err(|_e| CommonWebError::from_error(_e))?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/trending/list_trending_tts_models.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/trending/list_trending_tts_models.rs index c96031fc9f..8312f53d5b 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/trending/list_trending_tts_models.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/trending/list_trending_tts_models.rs @@ -13,7 +13,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::error; use enums::by_table::trending_model_analytics::window_name::WindowName; @@ -51,7 +51,7 @@ pub type WindowTrends = HashMap>; pub async fn list_trending_tts_models_handler( _http_request: HttpRequest, - server_state: web::Data>) -> Result + server_state: web::Data>) -> Result, CommonWebError> { // TODO: Cache the outputs! @@ -112,10 +112,5 @@ pub async fn list_trending_tts_models_handler( top_trending_by_language_code, }; - let body = serde_json::to_string(&response) - .map_err(CommonWebError::from_error)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/delete_tts_model.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/delete_tts_model.rs index 45a594e4b3..a4559816d5 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/delete_tts_model.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/delete_tts_model.rs @@ -4,9 +4,10 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; use actix_web::web::Path; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::warn; +use artcraft_api_defs::common::responses::simple_generic_json_success::SimpleGenericJsonSuccess; use http_server_common::request::get_request_ip::get_request_ip; use mysql_queries::queries::tts::tts_models::delete_tts_model_various_scopes::delete_tts_model_as_mod; use mysql_queries::queries::tts::tts_models::delete_tts_model_various_scopes::delete_tts_model_as_user; @@ -16,7 +17,6 @@ use mysql_queries::queries::tts::tts_models::get_tts_model::get_tts_model_by_tok use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::response_success_helpers::simple_json_success; use crate::state::server_state::ServerState; use crate::util::delete_role_disambiguation::delete_role_disambiguation; use crate::util::delete_role_disambiguation::DeleteRole; @@ -39,7 +39,7 @@ pub async fn delete_tts_model_handler( path: Path, request: web::Json, server_state: web::Data> -) -> Result { +) -> Result, CommonWebError> { // NB: Disable if we've migrated to model_weights if server_state.flags.switch_tts_to_model_weights { warn!("Migration to model_weights for tts. Cannot delete old model."); @@ -154,5 +154,5 @@ pub async fn delete_tts_model_handler( } }; - Ok(simple_json_success()) + Ok(web::Json(SimpleGenericJsonSuccess { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/delete_tts_result.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/delete_tts_result.rs index abad0907bf..d71b6f1907 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/delete_tts_result.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/delete_tts_result.rs @@ -4,9 +4,10 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; use actix_web::web::Path; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::warn; +use artcraft_api_defs::common::responses::simple_generic_json_success::SimpleGenericJsonSuccess; use mysql_queries::queries::tts::tts_results::delete_tts_result_various_scopes::delete_tts_inference_result_as_mod; use mysql_queries::queries::tts::tts_results::delete_tts_result_various_scopes::delete_tts_inference_result_as_user; use mysql_queries::queries::tts::tts_results::delete_tts_result_various_scopes::undelete_tts_inference_result_as_mod; @@ -15,7 +16,6 @@ use mysql_queries::queries::tts::tts_results::query_tts_result::select_tts_resul use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::response_success_helpers::simple_json_success; use crate::state::server_state::ServerState; use crate::util::delete_role_disambiguation::delete_role_disambiguation; use crate::util::delete_role_disambiguation::DeleteRole; @@ -38,7 +38,7 @@ pub async fn delete_tts_inference_result_handler( path: Path, request: web::Json, server_state: web::Data> -) -> Result { +) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker .maybe_get_user_session(&http_request, &server_state.mysql_pool) @@ -141,5 +141,5 @@ pub async fn delete_tts_inference_result_handler( } }; - Ok(simple_json_success()) + Ok(web::Json(SimpleGenericJsonSuccess { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/edit_tts_model.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/edit_tts_model.rs index 3f26e6f4cd..44ab796897 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/edit_tts_model.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/edit_tts_model.rs @@ -6,15 +6,15 @@ use std::sync::Arc; use actix_web::web::{Json, Path}; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use language_tags::LanguageTag; use log::{error, info, warn}; use sqlx::MySqlPool; use crate::configs::supported_languages_for_models::get_canonicalized_language_tag_for_model; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::response_success_helpers::simple_json_success; use crate::state::server_state::ServerState; +use artcraft_api_defs::common::responses::simple_generic_json_success::SimpleGenericJsonSuccess; use enums::common::visibility::Visibility; use http_server_common::request::get_request_ip::get_request_ip; use markdown::simple_markdown_to_html::simple_markdown_to_html; @@ -92,7 +92,7 @@ pub async fn edit_tts_model_handler( http_request: HttpRequest, path: Path, request: Json, - server_state: web::Data>) -> Result + server_state: web::Data>) -> Result, CommonWebError> { // NB: Disable if we've migrated to model_weights if server_state.flags.switch_tts_to_model_weights { @@ -300,7 +300,7 @@ pub async fn edit_tts_model_handler( let _r = redis_ttl_cache.delete_from_cache(&cache_key).ok(); } - Ok(simple_json_success()) + Ok(web::Json(SimpleGenericJsonSuccess { success: true })) } async fn update_mod_details( diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/edit_tts_result.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/edit_tts_result.rs index b82629cd56..abecf5900b 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/edit_tts_result.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/edit_tts_result.rs @@ -6,16 +6,16 @@ use std::sync::Arc; use actix_web::web::Path; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::{error, warn}; +use artcraft_api_defs::common::responses::simple_generic_json_success::SimpleGenericJsonSuccess; use enums::common::visibility::Visibility; use http_server_common::request::get_request_ip::get_request_ip; use mysql_queries::queries::tts::tts_results::edit_tts_result::{edit_tts_result, CreatorOrModFields, EditTtsResultArgs}; use mysql_queries::queries::tts::tts_results::query_tts_result::select_tts_result_by_token; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::response_success_helpers::simple_json_success; use crate::state::server_state::ServerState; /// For the URL PathInfo @@ -34,7 +34,7 @@ pub async fn edit_tts_inference_result_handler( http_request: HttpRequest, path: Path, request: web::Json, - server_state: web::Data>) -> Result + server_state: web::Data>) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -120,5 +120,5 @@ pub async fn edit_tts_inference_result_handler( CommonWebError::from_anyhow_error(err) })?; - Ok(simple_json_success()) + Ok(web::Json(SimpleGenericJsonSuccess { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_upload_tts_model.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_upload_tts_model.rs index 3434a89b6c..6dc76ba192 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_upload_tts_model.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_upload_tts_model.rs @@ -5,7 +5,7 @@ use std::sync::Arc; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::warn; use crate::http_server::validations::validate_model_title::validate_model_title; @@ -71,8 +71,8 @@ pub async fn upload_tts_model_handler( http_request: HttpRequest, request: web::Json, server_state: web::Data> -) -> Result { - +) -> Result, CommonWebError> { + if server_state.flags.disable_tts { return Err(CommonWebError::TooManyRequests); } @@ -186,18 +186,11 @@ pub async fn upload_tts_model_handler( CommonWebError::from_anyhow_error(e) })?; - let response = UploadTtsModelSuccessResponse { + Ok(web::Json(UploadTtsModelSuccessResponse { success: true, job_token: job_token.to_string(), job_type: download_job_type, - }; - - let body = serde_json::to_string(&response) - ?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + })) } fn validate_idempotency_token(token: &str) -> Result<(), String> { diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_inference_job_status.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_inference_job_status.rs index b6a2260fa3..50a4a724f1 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_inference_job_status.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_inference_job_status.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; use actix_web::web::Path; -use actix_web::{web, HttpMessage, HttpRequest, HttpResponse}; +use actix_web::{web, HttpMessage, HttpRequest}; use chrono::{DateTime, Utc}; use log::error; use redis::{Commands, RedisResult}; @@ -61,7 +61,7 @@ pub struct GetTtsInferenceStatusSuccessResponse { pub async fn get_tts_inference_job_status_handler( http_request: HttpRequest, path: Path, - server_state: web::Data>) -> Result + server_state: web::Data>) -> Result, CommonWebError> { let job_token = path.into_inner().token; @@ -80,20 +80,10 @@ pub async fn get_tts_inference_job_status_handler( legacy_lookup(&job_token, &server_state).await? }; - let response = GetTtsInferenceStatusSuccessResponse { + Ok(web::Json(GetTtsInferenceStatusSuccessResponse { success: true, state: record_for_response, - }; - - let body = serde_json::to_string(&response) - .map_err(|e| { - error!("error returning response: {:?}", e); - CommonWebError::from_error(e) - })?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + })) } async fn legacy_lookup(job_token: &str, server_state: &ServerState) diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_model.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_model.rs index 00824be2c0..01c5d7e670 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_model.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_model.rs @@ -6,7 +6,7 @@ use std::sync::Arc; use actix_web::web::Path; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use chrono::{DateTime, Utc}; use log::warn; @@ -152,7 +152,7 @@ pub struct TtsModelModeratorFieldInfo { pub async fn get_tts_model_handler( http_request: HttpRequest, path: Path, - server_state: web::Data>) -> Result + server_state: web::Data>) -> Result, CommonWebError> { let mut mysql_connection = server_state.mysql_pool.acquire() .await @@ -327,10 +327,5 @@ pub async fn get_tts_model_handler( }, }; - let body = serde_json::to_string(&response) - .map_err(CommonWebError::from_error)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_model_use_count.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_model_use_count.rs index eefae36524..d6d0bc2030 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_model_use_count.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_model_use_count.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; use actix_web::web::Path; -use actix_web::{web, HttpMessage, HttpRequest, HttpResponse}; +use actix_web::{web, HttpMessage, HttpRequest}; use derive_more::Display; use log::warn; use redis::Commands; @@ -28,7 +28,7 @@ pub struct GetTtsModelUseCountSuccessResponse { pub async fn get_tts_model_use_count_handler( http_request: HttpRequest, path: Path, - server_state: web::Data>) -> Result + server_state: web::Data>) -> Result, CommonWebError> { let mut redis = server_state.redis_pool .get() @@ -45,15 +45,8 @@ pub async fn get_tts_model_use_count_handler( CommonWebError::from_error(e) })?; - let response = GetTtsModelUseCountSuccessResponse { + Ok(web::Json(GetTtsModelUseCountSuccessResponse { success: true, count, - }; - - let body = serde_json::to_string(&response) - .map_err(CommonWebError::from_error)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_result.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_result.rs index bd7a0283ad..277989210a 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_result.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_result.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; use actix_web::web::Path; -use actix_web::{web, HttpMessage, HttpRequest, HttpResponse}; +use actix_web::{web, HttpMessage, HttpRequest}; use log::warn; use mysql_queries::queries::tts::tts_results::query_tts_result::select_tts_result_by_token; @@ -29,7 +29,7 @@ pub struct GetTtsResultSuccessResponse { pub async fn get_tts_inference_result_handler( http_request: HttpRequest, path: Path, - server_state: web::Data>) -> Result + server_state: web::Data>) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -80,15 +80,8 @@ pub async fn get_tts_inference_result_handler( inference_result.maybe_moderator_fields = None; } - let response = GetTtsResultSuccessResponse { + Ok(web::Json(GetTtsResultSuccessResponse { success: true, result: inference_result, - }; - - let body = serde_json::to_string(&response) - .map_err(|e| CommonWebError::from_error(e))?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_upload_model_job_status.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_upload_model_job_status.rs index a2dd7ddf90..839b1dd741 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_upload_model_job_status.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_upload_model_job_status.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; use actix_web::web::Path; -use actix_web::{web, HttpMessage, HttpRequest, HttpResponse}; +use actix_web::{web, HttpMessage, HttpRequest}; use chrono::{DateTime, Utc}; use derive_more::Display; use log::{error, warn}; @@ -49,7 +49,7 @@ pub struct GetTtsUploadModelStatusSuccessResponse { pub async fn get_tts_upload_model_job_status_handler( http_request: HttpRequest, path: Path, - server_state: web::Data>) -> Result + server_state: web::Data>) -> Result, CommonWebError> { // NB: Since this is publicly exposed, we don't query sensitive data. let record = get_tts_model_upload_job_status(&path.token, &server_state.mysql_pool) @@ -92,15 +92,8 @@ pub async fn get_tts_upload_model_job_status_handler( updated_at: record.updated_at, }; - let response = GetTtsUploadModelStatusSuccessResponse { + Ok(web::Json(GetTtsUploadModelStatusSuccessResponse { success: true, state: model_for_response, - }; - - let body = serde_json::to_string(&response) - .map_err(|e| CommonWebError::from_error(e))?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/search_tts_models_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/search_tts_models_handler.rs index 93878e9a6e..a733c18901 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/search_tts_models_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/search_tts_models_handler.rs @@ -6,7 +6,7 @@ use std::collections::HashSet; use std::sync::Arc; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use chrono::{DateTime, Utc}; use log::error; @@ -67,7 +67,7 @@ pub struct SearchTtsModelsSuccessResponse { pub async fn search_tts_models_handler( _http_request: HttpRequest, request: web::Json, - server_state: web::Data>) -> Result + server_state: web::Data>) -> Result, CommonWebError> { let results = search_tts_models( &server_state.elasticsearch, @@ -109,16 +109,9 @@ pub async fn search_tts_models_handler( new_results.push(result); } - let response = SearchTtsModelsSuccessResponse { + Ok(web::Json(SearchTtsModelsSuccessResponse { success: true, models: new_results, - }; - - let body = serde_json::to_string(&response) - .map_err(CommonWebError::from_error)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/create_user_bookmark_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/create_user_bookmark_handler.rs index a697a8ca44..2142681acd 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/create_user_bookmark_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/create_user_bookmark_handler.rs @@ -97,7 +97,7 @@ pub async fn create_user_bookmark_handler( http_request: HttpRequest, request: web::Json, server_state: web::Data>, -) -> Result +) -> Result, CreateUserBookmarkError> { // NB(bt,2023-12-14): Kasisnu found that we're getting entity type mismatches in production. Apart from // querying the database for entity existence, this is the next best way to prevent incorrect comment @@ -226,10 +226,5 @@ pub async fn create_user_bookmark_handler( new_bookmark_count_for_entity: count.total_count, }; - let body = serde_json::to_string(&response) - .map_err(|_e| CreateUserBookmarkError::ServerError)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/delete_user_bookmark_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/delete_user_bookmark_handler.rs index b86ba95adc..e7287756e3 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/delete_user_bookmark_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/delete_user_bookmark_handler.rs @@ -22,7 +22,6 @@ use tokens::tokens::user_bookmarks::UserBookmarkToken; use artcraft_api_defs::common::responses::simple_generic_json_success::SimpleGenericJsonSuccess; use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; -use crate::http_server::web_utils::response_success_helpers::simple_json_success; use crate::state::server_state::ServerState; /// For the URL PathInfo @@ -94,7 +93,7 @@ pub async fn delete_user_bookmark_handler( path: Path, _request: web::Json, server_state: web::Data> -) -> Result { +) -> Result, DeleteUserBookmarkError> { let mut mysql_connection = server_state.mysql_pool .acquire() .await @@ -181,5 +180,5 @@ pub async fn delete_user_bookmark_handler( } }; - Ok(simple_json_success()) + Ok(web::Json(SimpleGenericJsonSuccess { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/list_user_bookmarks_for_entity_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/list_user_bookmarks_for_entity_handler.rs index d99c9ac5fe..cb78d8714c 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/list_user_bookmarks_for_entity_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/list_user_bookmarks_for_entity_handler.rs @@ -92,7 +92,7 @@ pub async fn list_user_bookmarks_for_entity_handler( _http_request: HttpRequest, path: Path, server_state: web::Data> -) -> Result +) -> Result, ListUserBookmarksForEntityError> { let entity_token = UserBookmarkEntityToken::from_entity_type_and_token( path.entity_type, &path.entity_token); @@ -128,10 +128,5 @@ pub async fn list_user_bookmarks_for_entity_handler( .collect(), }; - let body = serde_json::to_string(&response) - .map_err(|_e| ListUserBookmarksForEntityError::ServerError)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/list_user_bookmarks_for_session_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/list_user_bookmarks_for_session_handler.rs index b427cdbcee..3c4620d04e 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/list_user_bookmarks_for_session_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/list_user_bookmarks_for_session_handler.rs @@ -84,7 +84,7 @@ pub async fn list_user_bookmarks_for_session_handler( http_request: HttpRequest, query: Query, server_state: web::Data> -) -> Result +) -> Result, ListUserBookmarksError> { let user_session = server_state.session_checker .maybe_get_user_session(&http_request, &server_state.mysql_pool) @@ -108,10 +108,5 @@ pub async fn list_user_bookmarks_for_session_handler( user_bookmarks: Vec::new() }; - let body = serde_json::to_string(&response) - .map_err(|_e| ListUserBookmarksError::ServerError)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/user_ratings/get_user_rating_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/user_ratings/get_user_rating_handler.rs index 39d5c5852b..9060323c2c 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/user_ratings/get_user_rating_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/user_ratings/get_user_rating_handler.rs @@ -1,13 +1,11 @@ use std::sync::Arc; -use actix_web::http::StatusCode; -use actix_web::{web, HttpRequest, HttpResponse, ResponseError}; +use actix_web::{web, HttpRequest}; use log::{error, info}; use utoipa::ToSchema; use enums::by_table::user_ratings::entity_type::UserRatingEntityType; use enums::by_table::user_ratings::rating_value::UserRatingValue; -use http_server_common::response::serialize_as_json_error::serialize_as_json_error; use mysql_queries::composite_keys::by_table::user_ratings::user_rating_entity::UserRatingEntity; use mysql_queries::queries::users::user_ratings::get_user_rating::{get_user_rating, Args}; use tokens::tokens::media_files::MediaFileToken; @@ -64,7 +62,7 @@ pub struct GetUserRatingResponse { pub async fn get_user_rating_handler( http_request: HttpRequest, path: web::Path, - server_state: web::Data>) -> Result + server_state: web::Data>) -> Result, CommonWebError> { let mut mysql_connection = server_state.mysql_pool.acquire() .await @@ -121,10 +119,5 @@ pub async fn get_user_rating_handler( maybe_rating_value: maybe_rating.map(|rating| rating.rating_value), }; - let body = serde_json::to_string(&response) - .map_err(CommonWebError::from_error)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/user_ratings/set_user_rating_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/user_ratings/set_user_rating_handler.rs index 5d3fc88b9b..0abf554668 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/user_ratings/set_user_rating_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/user_ratings/set_user_rating_handler.rs @@ -1,7 +1,6 @@ use std::sync::Arc; -use actix_web::http::StatusCode; -use actix_web::{web, HttpRequest, HttpResponse, ResponseError}; +use actix_web::{web, HttpRequest}; use log::{error, info, warn}; use sqlx::Acquire; use utoipa::ToSchema; @@ -9,7 +8,6 @@ use utoipa::ToSchema; use enums::by_table::user_ratings::entity_type::UserRatingEntityType; use enums::by_table::user_ratings::rating_value::UserRatingValue; use http_server_common::request::get_request_ip::get_request_ip; -use http_server_common::response::serialize_as_json_error::serialize_as_json_error; use mysql_queries::composite_keys::by_table::user_ratings::user_rating_entity::UserRatingEntity; use mysql_queries::queries::entity_stats::stats_entity_token::StatsEntityToken; use mysql_queries::queries::entity_stats::upsert_entity_stats_on_ratings_event::{upsert_entity_stats_on_ratings_event, RatingsAction, UpsertEntityStatsArgs}; @@ -70,7 +68,7 @@ pub struct SetUserRatingResponse { pub async fn set_user_rating_handler( http_request: HttpRequest, request: web::Json, - server_state: web::Data>) -> Result + server_state: web::Data>) -> Result, CommonWebError> { // NB(bt,2023-12-14): Kasisnu found that we're getting entity type mismatches in production. Apart from // querying the database for entity existence, this is the next best way to prevent incorrect comment @@ -239,10 +237,5 @@ pub async fn set_user_rating_handler( new_positive_rating_count_for_entity: count.positive_count, }; - let body = serde_json::to_string(&response) - .map_err(CommonWebError::from_error)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/users/edit_profile_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/users/edit_profile_handler.rs index 58cbd7447f..2a60d2f873 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/users/edit_profile_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/users/edit_profile_handler.rs @@ -5,7 +5,7 @@ use actix_web::web::Path; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::warn; use sqlx::MySqlPool; @@ -65,7 +65,7 @@ pub async fn edit_profile_handler( mysql_pool: web::Data, redis_ttl_cache: web::Data, session_checker: web::Data, -) -> Result +) -> Result, CommonWebError> { let maybe_user_session = session_checker .maybe_get_user_session(&http_request, &mysql_pool) @@ -285,10 +285,5 @@ pub async fn edit_profile_handler( success: true, }; - let body = serde_json::to_string(&response) - .map_err(|_e| CommonWebError::BadInputWithSimpleMessage("".to_string()))?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_clone_requests/check_if_voice_clone_request_submitted.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_clone_requests/check_if_voice_clone_request_submitted.rs index a952160e10..10192e2b9f 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_clone_requests/check_if_voice_clone_request_submitted.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_clone_requests/check_if_voice_clone_request_submitted.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::warn; use http_server_common::response::serialize_as_json_error::serialize_as_json_error; @@ -36,7 +36,7 @@ pub struct CheckIfVoiceRequestSubmittedResponse { pub async fn check_if_voice_clone_request_submitted_handler( http_request: HttpRequest, request: web::Json, - server_state: web::Data>) -> Result + server_state: web::Data>) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -83,10 +83,5 @@ pub async fn check_if_voice_clone_request_submitted_handler( has_submitted, }; - let body = serde_json::to_string(&response) - .map_err(CommonWebError::from_error)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_clone_requests/create_voice_clone_request.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_clone_requests/create_voice_clone_request.rs index 648076d9fe..441acea828 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_clone_requests/create_voice_clone_request.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_clone_requests/create_voice_clone_request.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::warn; use http_server_common::request::get_request_ip::get_request_ip; @@ -65,7 +65,7 @@ pub struct CreateVoiceRequestResponse { pub async fn create_voice_clone_request_handler( http_request: HttpRequest, request: web::Json, - server_state: web::Data>) -> Result + server_state: web::Data>) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -113,10 +113,5 @@ pub async fn create_voice_clone_request_handler( success: true, }; - let body = serde_json::to_string(&response) - .map_err(CommonWebError::from_error)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/inference/enqueue_tts_request.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/inference/enqueue_tts_request.rs index f4892bb727..68353d1cbe 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/inference/enqueue_tts_request.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/inference/enqueue_tts_request.rs @@ -4,7 +4,7 @@ use std::sync::Arc; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::warn; use serde::Deserialize; use serde::Serialize; @@ -78,8 +78,8 @@ pub async fn enqueue_tts_request( http_request: HttpRequest, request: web::Json, server_state: web::Data> -) -> Result { - +) -> Result, CommonWebError> { + let mut maybe_user_token: Option = None; let mut mysql_connection = server_state.mysql_pool.acquire().await.map_err(|err| { @@ -194,8 +194,5 @@ pub async fn enqueue_tts_request( inference_job_token: job_token, }; - let body = serde_json::to_string(&response)?; - - // Error handling 101 rust result type returned like so. - Ok(HttpResponse::Ok().content_type("application/json").body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_dataset_samples/delete_sample.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_dataset_samples/delete_sample.rs index 6cedc383ef..21bbda98a1 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_dataset_samples/delete_sample.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_dataset_samples/delete_sample.rs @@ -1,12 +1,10 @@ use std::sync::Arc; -use actix_web::http::StatusCode; use actix_web::web::Path; -use actix_web::{web, HttpRequest, HttpResponse, ResponseError}; +use actix_web::{web, HttpRequest}; use log::warn; -use http_server_common::response::response_error_helpers::to_simple_json_error; -use http_server_common::response::response_success_helpers::simple_json_success; +use artcraft_api_defs::common::responses::simple_generic_json_success::SimpleGenericJsonSuccess; use mysql_queries::queries::voice_designer::voice_samples::delete_sample::{delete_sample_as_mod, delete_sample_as_user, undelete_sample_as_mod, undelete_sample_as_user}; use mysql_queries::queries::voice_designer::voice_samples::get_dataset_sample::get_dataset_sample_by_token; use tokens::tokens::zs_voice_dataset_samples::ZsVoiceDatasetSampleToken; @@ -35,7 +33,7 @@ pub async fn delete_sample_handler( path: Path, request: web::Json, server_state: web::Data> -) -> Result { +) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -122,5 +120,5 @@ pub async fn delete_sample_handler( } }; - Ok(simple_json_success()) + Ok(web::Json(SimpleGenericJsonSuccess { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_dataset_samples/list_samples_by_dataset.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_dataset_samples/list_samples_by_dataset.rs index c34747aa0b..b20aaedc5d 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_dataset_samples/list_samples_by_dataset.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_dataset_samples/list_samples_by_dataset.rs @@ -1,8 +1,6 @@ use std::sync::Arc; -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use chrono::{DateTime, Utc}; use log::warn; @@ -47,7 +45,7 @@ pub async fn list_samples_by_dataset_handler( http_request: HttpRequest, path: web::Path, server_state: web::Data> -) -> Result { +) -> Result, CommonWebError> { let maybe_user_session = server_state.session_checker.maybe_get_user_session( &http_request, &server_state.mysql_pool @@ -108,10 +106,5 @@ pub async fn list_samples_by_dataset_handler( samples }; - let body = serde_json::to_string(&response).map_err(|e| { - warn!("json serialization error: {:?}", e); - CommonWebError::from_error(e) - })?; - - Ok(HttpResponse::Ok().content_type("application/json").body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/create_dataset.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/create_dataset.rs index 33c5afbc61..c6dfe6e254 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/create_dataset.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/create_dataset.rs @@ -1,14 +1,11 @@ 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, HttpRequest}; use log::{error, warn}; use enums::common::visibility::Visibility; use http_server_common::request::get_request_ip::get_request_ip; -use http_server_common::response::serialize_as_json_error::serialize_as_json_error; use mysql_queries::queries::voice_designer::datasets::create_dataset::{create_dataset, CreateDatasetArgs}; use tokens::tokens::zs_voice_datasets::ZsVoiceDatasetToken; @@ -34,7 +31,7 @@ pub struct CreateDatasetResponse { // NB: Not using DeriveMore since Clion doesn't understand it. // =============== Handler =============== -pub async fn create_dataset_handler(http_request: HttpRequest, request: web::Json, server_state: web::Data>) -> Result { +pub async fn create_dataset_handler(http_request: HttpRequest, request: web::Json, server_state: web::Data>) -> Result, CommonWebError> { let maybe_user_session = server_state.session_checker.maybe_get_user_session(&http_request, &server_state.mysql_pool).await.map_err(|e| { error!("Error getting user session: {:?}", e); CommonWebError::from_error(e) @@ -78,10 +75,5 @@ pub async fn create_dataset_handler(http_request: HttpRequest, request: web::Jso }; - let body = serde_json::to_string(&response) - .map_err(|e| CommonWebError::from_error(e))?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/delete_dataset.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/delete_dataset.rs index 02680799f6..0bae688f3c 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/delete_dataset.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/delete_dataset.rs @@ -1,18 +1,15 @@ use std::fmt; use std::sync::Arc; -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; use actix_web::web::Path; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::warn; -use http_server_common::response::serialize_as_json_error::serialize_as_json_error; +use artcraft_api_defs::common::responses::simple_generic_json_success::SimpleGenericJsonSuccess; use mysql_queries::queries::voice_designer::datasets::delete_dataset::{delete_dataset_as_mod, delete_dataset_as_user, undelete_dataset_as_mod, undelete_dataset_as_user}; use mysql_queries::queries::voice_designer::datasets::get_dataset::get_dataset_by_token; use tokens::tokens::zs_voice_datasets::ZsVoiceDatasetToken; -use crate::http_server::web_utils::response_success_helpers::simple_json_success; use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::state::server_state::ServerState; use crate::util::delete_role_disambiguation::{delete_role_disambiguation, DeleteRole}; @@ -39,7 +36,7 @@ pub async fn delete_dataset_handler( path: Path, request: web::Json, server_state: web::Data> -) -> Result{ +) -> Result, CommonWebError>{ let maybe_user_session = server_state .session_checker .maybe_get_user_session(&http_request, &server_state.mysql_pool) @@ -140,6 +137,6 @@ pub async fn delete_dataset_handler( } }; - Ok(simple_json_success()) + Ok(web::Json(SimpleGenericJsonSuccess { success: true })) } \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/get_dataset.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/get_dataset.rs index 216eb7c368..b3be160233 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/get_dataset.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/get_dataset.rs @@ -1,10 +1,8 @@ use std::fmt; use std::sync::Arc; -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; use actix_web::web::Path; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use chrono::{DateTime, Utc}; use log::warn; @@ -42,7 +40,7 @@ pub async fn get_dataset_handler( http_request: HttpRequest, path: Path, server_state: web::Data> -) -> Result { +) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -109,10 +107,5 @@ pub async fn get_dataset_handler( updated_at: dataset.updated_at, }; - let body = serde_json::to_string(&response) - .map_err(CommonWebError::from_error)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/list_datasets_by_session.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/list_datasets_by_session.rs index 88b789bf2b..1e7146dae4 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/list_datasets_by_session.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/list_datasets_by_session.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use chrono::{DateTime, Utc}; use log::warn; @@ -61,7 +61,7 @@ impl ResponseError for ListDatasetsByUserError { pub async fn list_datasets_by_session_handler( http_request: HttpRequest, server_state: web::Data> -) -> Result { +) -> Result, ListDatasetsByUserError> { let user_session = server_state .session_checker @@ -122,10 +122,5 @@ pub async fn list_datasets_by_session_handler( datasets, }; - let body = serde_json::to_string(&response) - .map_err(|e| ListDatasetsByUserError::ServerError)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/list_datasets_by_user.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/list_datasets_by_user.rs index c7d92c08dd..cbf51672da 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/list_datasets_by_user.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/list_datasets_by_user.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; use actix_web::web::Path; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use chrono::{DateTime, Utc}; use log::warn; use utoipa::ToSchema; @@ -82,7 +82,7 @@ pub async fn list_datasets_by_user_handler( http_request: HttpRequest, path: Path, server_state: web::Data> -) -> Result { +) -> Result, ListDatasetsByUserError> { let maybe_user_session = server_state .session_checker @@ -148,10 +148,5 @@ pub async fn list_datasets_by_user_handler( datasets, }; - let body = serde_json::to_string(&response) - .map_err(|e| ListDatasetsByUserError::ServerError)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/update_dataset.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/update_dataset.rs index 37a437fb80..cfe2a62cbb 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/update_dataset.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/update_dataset.rs @@ -1,16 +1,13 @@ use std::fmt; use std::sync::Arc; -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; use actix_web::web::Path; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::{error, warn}; +use artcraft_api_defs::common::responses::simple_generic_json_success::SimpleGenericJsonSuccess; use enums::common::visibility::Visibility; use http_server_common::request::get_request_ip::get_request_ip; -use http_server_common::response::response_success_helpers::simple_json_success; -use http_server_common::response::serialize_as_json_error::serialize_as_json_error; use mysql_queries::queries::voice_designer::datasets::get_dataset::get_dataset_by_token; use mysql_queries::queries::voice_designer::datasets::update_dataset::{update_dataset, UpdateDatasetArgs}; use tokens::tokens::zs_voice_datasets::ZsVoiceDatasetToken; @@ -49,7 +46,7 @@ pub async fn update_dataset_handler( http_request: HttpRequest, path: Path, request: web::Json, - server_state: web::Data>) -> Result + server_state: web::Data>) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -169,5 +166,5 @@ pub async fn update_dataset_handler( } }; - Ok(simple_json_success()) + Ok(web::Json(SimpleGenericJsonSuccess { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/create_voice.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/create_voice.rs index b26dd52eb7..539dbf52e8 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/create_voice.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/create_voice.rs @@ -5,9 +5,7 @@ use std::fmt::Debug; use std::sync::Arc; -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::{info, warn}; use serde::Deserialize; use serde::Serialize; @@ -33,7 +31,6 @@ use tokens::tokens::users::UserToken; use crate::configs::plans::get_correct_plan_for_session::get_correct_plan_for_session; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; use crate::state::server_state::ServerState; #[derive(Deserialize)] @@ -60,7 +57,7 @@ pub async fn create_voice_handler( http_request: HttpRequest, request: web::Json, server_state: web::Data> -) -> Result { +) -> Result, CommonWebError> { // voice data set token ... zsd_wb08asm0m4a4cz42wwqked6tfg6e e.g // need this for the job @@ -184,9 +181,5 @@ pub async fn create_voice_handler( inference_job_token: job_token, }; - let body = serde_json - ::to_string(&response) - ?; - // Error handling 101 rust result type returned like so. - Ok(HttpResponse::Ok().content_type("application/json").body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/delete_voice.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/delete_voice.rs index dbcc9f3256..92f566062b 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/delete_voice.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/delete_voice.rs @@ -1,18 +1,15 @@ use std::fmt; use std::sync::Arc; -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; use actix_web::web::Path; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::warn; -use http_server_common::response::serialize_as_json_error::serialize_as_json_error; +use artcraft_api_defs::common::responses::simple_generic_json_success::SimpleGenericJsonSuccess; use mysql_queries::queries::voice_designer::voices::delete_voice::{delete_voice_as_mod, delete_voice_as_user, undelete_voice_as_mod, undelete_voice_as_user}; use mysql_queries::queries::voice_designer::voices::get_voice::get_voice_by_token; use tokens::tokens::zs_voices::ZsVoiceToken; -use crate::http_server::web_utils::response_success_helpers::simple_json_success; use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::state::server_state::ServerState; use crate::util::delete_role_disambiguation::{delete_role_disambiguation, DeleteRole}; @@ -39,7 +36,7 @@ pub async fn delete_voice_handler( path: Path, request: web::Json, server_state: web::Data> -) -> Result{ +) -> Result, CommonWebError>{ let maybe_user_session = server_state .session_checker .maybe_get_user_session(&http_request, &server_state.mysql_pool) @@ -139,5 +136,5 @@ pub async fn delete_voice_handler( } }; - Ok(simple_json_success()) + Ok(web::Json(SimpleGenericJsonSuccess { success: true })) } \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/get_voice.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/get_voice.rs index 1136685112..d58150a742 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/get_voice.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/get_voice.rs @@ -1,10 +1,8 @@ use std::fmt; use std::sync::Arc; -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; use actix_web::web::Path; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use chrono::{DateTime, Utc}; use log::warn; @@ -42,7 +40,7 @@ pub async fn get_voice_handler( http_request: HttpRequest, path: Path, server_state: web::Data> -) -> Result { +) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -109,10 +107,5 @@ pub async fn get_voice_handler( updated_at: voice.updated_at, }; - let body = serde_json::to_string(&response) - .map_err(CommonWebError::from_error)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/list_available_voices.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/list_available_voices.rs index 68456e37fc..1eca058bc7 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/list_available_voices.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/list_available_voices.rs @@ -1,8 +1,6 @@ use std::sync::Arc; -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use chrono::{DateTime, Utc}; use log::{info, warn}; @@ -48,7 +46,7 @@ pub async fn list_available_voices_handler( http_request: HttpRequest, query: web::Query, server_state: web::Data> -) -> Result { +) -> Result, CommonWebError> { let maybe_user_session = server_state.session_checker.maybe_get_user_session( &http_request, @@ -158,12 +156,7 @@ pub async fn list_available_voices_handler( cursor_previous, }; - let body = serde_json::to_string(&response) - .map_err(CommonWebError::from_error)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/list_voices_by_session.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/list_voices_by_session.rs index ef34ff9f6a..70df37978e 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/list_voices_by_session.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/list_voices_by_session.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use chrono::{DateTime, Utc}; use log::warn; @@ -60,7 +60,7 @@ impl ResponseError for ListVoicesByUserError { pub async fn list_voices_by_session_handler( http_request: HttpRequest, server_state: web::Data> -) -> Result { +) -> Result, ListVoicesByUserError> { let user_session = server_state .session_checker @@ -121,10 +121,5 @@ pub async fn list_voices_by_session_handler( voices, }; - let body = serde_json::to_string(&response) - .map_err(|e| ListVoicesByUserError::ServerError)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/list_voices_by_user.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/list_voices_by_user.rs index 757bbcaec0..ac8978f02c 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/list_voices_by_user.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/list_voices_by_user.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; use actix_web::web::Path; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use chrono::{DateTime, Utc}; use log::warn; @@ -68,7 +68,7 @@ pub async fn list_voices_by_user_handler( http_request: HttpRequest, path: Path, server_state: web::Data> -) -> Result { +) -> Result, ListVoicesByUserError> { let maybe_user_session = server_state .session_checker @@ -134,12 +134,7 @@ pub async fn list_voices_by_user_handler( voices, }; - let body = serde_json::to_string(&response) - .map_err(|e| ListVoicesByUserError::ServerError)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/update_voice.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/update_voice.rs index 513df6d9d4..6b36550f05 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/update_voice.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/update_voice.rs @@ -1,15 +1,12 @@ use std::fmt; use std::sync::Arc; -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; use actix_web::web::Path; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::{error, warn}; +use artcraft_api_defs::common::responses::simple_generic_json_success::SimpleGenericJsonSuccess; use enums::common::visibility::Visibility; -use http_server_common::response::response_success_helpers::simple_json_success; -use http_server_common::response::serialize_as_json_error::serialize_as_json_error; use mysql_queries::queries::voice_designer::voices::get_voice::get_voice_by_token; use mysql_queries::queries::voice_designer::voices::update_voice::{update_voice, UpdateVoiceArgs}; use tokens::tokens::zs_voices::ZsVoiceToken; @@ -48,7 +45,7 @@ pub async fn update_voice_handler( http_request: HttpRequest, path: Path, request: web::Json, - server_state: web::Data>) -> Result + server_state: web::Data>) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -166,5 +163,5 @@ pub async fn update_voice_handler( } }; - Ok(simple_json_success()) + Ok(web::Json(SimpleGenericJsonSuccess { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/delete/delete_weight_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/delete/delete_weight_handler.rs index af62499971..a0aa0c0188 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/delete/delete_weight_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/delete/delete_weight_handler.rs @@ -1,14 +1,10 @@ -use std::fmt; use std::sync::Arc; -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; use actix_web::web::Path; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::warn; use utoipa::ToSchema; -use http_server_common::response::serialize_as_json_error::serialize_as_json_error; use mysql_queries::queries::model_weights::delete_weights::{ delete_weights_as_mod, delete_weights_as_user, @@ -18,7 +14,6 @@ use mysql_queries::queries::model_weights::get::get_weight::get_weight_by_token; use tokens::tokens::model_weights::ModelWeightToken; use artcraft_api_defs::common::responses::simple_generic_json_success::SimpleGenericJsonSuccess; -use crate::http_server::web_utils::response_success_helpers::simple_json_success; use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::state::server_state::ServerState; use crate::util::delete_role_disambiguation::{delete_role_disambiguation, DeleteRole}; @@ -57,7 +52,7 @@ pub async fn delete_weight_handler( path: Path, request: web::Json, server_state: web::Data> - ) -> Result{ + ) -> Result, CommonWebError>{ let maybe_user_session = server_state .session_checker .maybe_get_user_session(&http_request, &server_state.mysql_pool) @@ -155,5 +150,5 @@ pub async fn delete_weight_handler( } }; - Ok(simple_json_success()) + Ok(web::Json(SimpleGenericJsonSuccess { success: true })) } \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/get/get_weight_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/get/get_weight_handler.rs index 5013c38322..59a755d18c 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/get/get_weight_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/get/get_weight_handler.rs @@ -1,10 +1,7 @@ -use std::fmt; use std::sync::Arc; -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; use actix_web::web::Path; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use chrono::{DateTime, Utc}; use log::warn; use utoipa::ToSchema; @@ -105,7 +102,7 @@ pub async fn get_weight_handler( http_request: HttpRequest, path: Path, server_state: web::Data> -) -> Result { +) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker .maybe_get_user_session(&http_request, &server_state.mysql_pool) @@ -216,10 +213,5 @@ pub async fn get_weight_handler( updated_at: weight.updated_at, }; - let body = serde_json::to_string(&response) - .map_err(CommonWebError::from_error)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_available_weights_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_available_weights_handler.rs index 191e17bfc2..5e255fc50b 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_available_weights_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_available_weights_handler.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use chrono::{DateTime, Utc}; use log::warn; use rand::Rng; @@ -142,7 +142,7 @@ pub async fn list_available_weights_handler( http_request: HttpRequest, query: web::Query, server_state: web::Data> -) -> Result { +) -> Result, impl ResponseError> { let maybe_user_session = server_state.session_checker .maybe_get_user_session(&http_request, &server_state.mysql_pool).await .map_err(|e| { @@ -296,8 +296,5 @@ pub async fn list_available_weights_handler( }, }; - let body = serde_json::to_string(&response) - .map_err(|e| ListWeightError::ServerError)?; - - Ok(HttpResponse::Ok().content_type("application/json").body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_featured_weights_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_featured_weights_handler.rs index 7cde33a36f..988973690d 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_featured_weights_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_featured_weights_handler.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; use actix_web::web::Query; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use chrono::{DateTime, Utc}; use log::warn; use utoipa::{IntoParams, ToSchema}; @@ -140,7 +140,7 @@ pub async fn list_featured_weights_handler( http_request: HttpRequest, query: Query, server_state: web::Data> -) -> Result { +) -> Result, impl ResponseError> { let maybe_user_session = server_state .session_checker @@ -280,10 +280,5 @@ pub async fn list_featured_weights_handler( }).collect::>(), }; - let body = serde_json::to_string(&response) - .map_err(|e| ListFeaturedWeightsError::ServerError)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_pinned_weights_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_pinned_weights_handler.rs index ab375608d8..8dcc9d0310 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_pinned_weights_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_pinned_weights_handler.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; use actix_web::http::StatusCode; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use chrono::{DateTime, Utc}; use log::{debug, error, warn}; use redis::Commands; @@ -108,7 +108,7 @@ impl ResponseError for ListPinnedWeightsError { pub async fn list_pinned_weights_handler( http_request: HttpRequest, server_state: web::Data> -) -> Result { +) -> Result, impl ResponseError> { let mut redis = server_state.redis_pool.get() .map_err(|err| { @@ -201,10 +201,5 @@ pub async fn list_pinned_weights_handler( }).collect::>(), }; - let body = serde_json::to_string(&response) - .map_err(|e| ListPinnedWeightsError::ServerError)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_weights_by_user_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_weights_by_user_handler.rs index 528eeaf11d..6ef628657c 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_weights_by_user_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_weights_by_user_handler.rs @@ -1,10 +1,7 @@ -use std::fmt; use std::sync::Arc; -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; use actix_web::web::{Path, Query}; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use chrono::{DateTime, Utc}; use log::warn; use utoipa::{IntoParams, ToSchema}; @@ -124,7 +121,7 @@ pub async fn list_weights_by_user_handler( path: Path, query: Query, server_state: web::Data> -) -> Result { +) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -248,10 +245,5 @@ pub async fn list_weights_by_user_handler( }; - let body = serde_json::to_string(&response) - .map_err(CommonWebError::from_error)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + Ok(web::Json(response)) } \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/update/set_model_weight_cover_image_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/update/set_model_weight_cover_image_handler.rs index 99d410c59e..7f51a764fc 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/update/set_model_weight_cover_image_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/update/set_model_weight_cover_image_handler.rs @@ -1,17 +1,12 @@ -use std::fmt; use std::sync::Arc; -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; use actix_web::web::Path; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::warn; use utoipa::ToSchema; use enums::by_table::media_files::media_file_type::MediaFileType; use http_server_common::request::get_request_ip::get_request_ip; -use http_server_common::response::response_success_helpers::simple_json_success; -use http_server_common::response::serialize_as_json_error::serialize_as_json_error; use mysql_queries::queries::media_files::get::get_media_file::get_media_file; use mysql_queries::queries::model_weights::edit::set_model_weight_cover_image::{set_model_weight_cover_image, UpdateArgs}; use mysql_queries::queries::model_weights::get::get_weight::get_weight_by_token; @@ -60,7 +55,7 @@ pub async fn set_model_weight_cover_image_handler( http_request: HttpRequest, path: Path, request: web::Json, - server_state: web::Data>) -> Result + server_state: web::Data>) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -165,5 +160,5 @@ pub async fn set_model_weight_cover_image_handler( } }; - Ok(simple_json_success()) + Ok(web::Json(SetModelWeightCoverImageResponse { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/update/update_weight_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/update/update_weight_handler.rs index 04a34c8b20..57d21f99de 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/update/update_weight_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/update/update_weight_handler.rs @@ -1,10 +1,7 @@ -use std::fmt; use std::sync::Arc; -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; use actix_web::web::Path; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::warn; use sqlx::MySqlPool; use utoipa::ToSchema; @@ -16,8 +13,7 @@ use crate::state::server_state::ServerState; use enums::by_table::media_files::media_file_type::MediaFileType; use enums::by_table::model_weights::weights_types::WeightsType; use enums::common::visibility::Visibility; -use http_server_common::response::response_success_helpers::{simple_json_success, SimpleGenericJsonSuccess}; -use http_server_common::response::serialize_as_json_error::serialize_as_json_error; +use http_server_common::response::response_success_helpers::SimpleGenericJsonSuccess; use markdown::simple_markdown_to_html::simple_markdown_to_html; use mysql_queries::queries::media_files::get::get_media_file::get_media_file; use mysql_queries::queries::model_weights::edit::update_weight::{update_weights, CoverImageOption, UpdateWeightArgs}; @@ -85,7 +81,7 @@ pub async fn update_weight_handler( request: web::Json, mysql_pool: web::Data, server_state: web::Data> -) -> Result { +) -> Result, CommonWebError> { let mut mysql_connection = mysql_pool .acquire() .await @@ -222,5 +218,5 @@ pub async fn update_weight_handler( } } - Ok(simple_json_success()) + Ok(web::Json(SimpleGenericJsonSuccess { success: true })) } From de538ba94c957735ededd7bead9ef8c47e62b838 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Tue, 16 Jun 2026 00:37:23 -0400 Subject: [PATCH 09/53] package import --- .../endpoints/analytics/log_browser_session_handler.rs | 7 ++++--- .../endpoints/beta_keys/create_beta_keys_handler.rs | 7 ++++--- .../beta_keys/edit_beta_key_distributed_flag_handler.rs | 7 ++++--- .../endpoints/beta_keys/edit_beta_key_note_handler.rs | 7 ++++--- .../endpoints/beta_keys/list_beta_keys_handler.rs | 5 +++-- .../endpoints/beta_keys/redeem_beta_key_handler.rs | 7 ++++--- .../endpoints/comments/create_comment_handler.rs | 7 ++++--- .../endpoints/comments/delete_comment_handler.rs | 7 ++++--- .../endpoints/comments/list_comments_handler.rs | 5 +++-- .../endpoints/download_job/enqueue_generic_download.rs | 7 ++++--- .../download_job/get_generic_upload_job_status.rs | 5 +++-- .../featured_items/create_featured_item_handler.rs | 7 ++++--- .../featured_items/delete_featured_item_handler.rs | 7 ++++--- .../inference_job/admin/kill_inference_jobs_handler.rs | 7 ++++--- .../delete/terminate_inference_job_handler.rs | 5 +++-- .../edit/change_media_file_visibility_handler.rs | 7 ++++--- .../media_files/edit/rename_media_file_handler.rs | 7 ++++--- .../media_files/edit/set_media_file_cover_image_handler.rs | 7 ++++--- .../media_files/edit/update_media_file_handler.rs | 7 ++++--- .../moderation/info/moderator_token_info_handler.rs | 5 +++-- .../http_server/endpoints/moderation/ip_bans/add_ip_ban.rs | 7 ++++--- .../endpoints/moderation/ip_bans/delete_ip_ban.rs | 7 ++++--- .../http_server/endpoints/moderation/ip_bans/get_ip_ban.rs | 5 +++-- .../endpoints/moderation/ip_bans/list_ip_bans.rs | 5 +++-- .../moderation/jobs/get_tts_inference_queue_count.rs | 5 +++-- .../moderation/jobs/get_w2l_inference_queue_count.rs | 4 ++-- .../endpoints/moderation/jobs/kill_tts_inference_jobs.rs | 7 ++++--- .../moderator_edit_user_feature_flags_handler.rs | 4 ++-- .../endpoints/subscriptions/unsubscribe_reason.rs | 7 ++++--- .../endpoints/trending/list_trending_tts_models.rs | 5 +++-- .../src/http_server/endpoints/tts/delete_tts_model.rs | 7 ++++--- .../src/http_server/endpoints/tts/delete_tts_result.rs | 7 ++++--- .../src/http_server/endpoints/tts/edit_tts_model.rs | 4 ++-- .../src/http_server/endpoints/tts/edit_tts_result.rs | 7 ++++--- .../http_server/endpoints/tts/enqueue_upload_tts_model.rs | 7 ++++--- .../endpoints/tts/get_tts_inference_job_status.rs | 5 +++-- .../src/http_server/endpoints/tts/get_tts_model.rs | 5 +++-- .../http_server/endpoints/tts/get_tts_model_use_count.rs | 5 +++-- .../src/http_server/endpoints/tts/get_tts_result.rs | 5 +++-- .../endpoints/tts/get_tts_upload_model_job_status.rs | 5 +++-- .../http_server/endpoints/tts/search_tts_models_handler.rs | 7 ++++--- .../user_bookmarks/create_user_bookmark_handler.rs | 7 ++++--- .../user_bookmarks/delete_user_bookmark_handler.rs | 7 ++++--- .../list_user_bookmarks_for_entity_handler.rs | 5 +++-- .../list_user_bookmarks_for_session_handler.rs | 5 +++-- .../endpoints/user_ratings/get_user_rating_handler.rs | 5 +++-- .../endpoints/user_ratings/set_user_rating_handler.rs | 7 ++++--- .../http_server/endpoints/users/edit_profile_handler.rs | 7 ++++--- .../check_if_voice_clone_request_submitted.rs | 7 ++++--- .../voice_clone_requests/create_voice_clone_request.rs | 7 ++++--- .../voice_designer/inference/enqueue_tts_request.rs | 7 ++++--- .../voice_designer/voice_dataset_samples/delete_sample.rs | 7 ++++--- .../voice_dataset_samples/list_samples_by_dataset.rs | 5 +++-- .../voice_designer/voice_datasets/create_dataset.rs | 5 +++-- .../voice_designer/voice_datasets/delete_dataset.rs | 7 ++++--- .../endpoints/voice_designer/voice_datasets/get_dataset.rs | 5 +++-- .../voice_datasets/list_datasets_by_session.rs | 5 +++-- .../voice_designer/voice_datasets/list_datasets_by_user.rs | 5 +++-- .../voice_designer/voice_datasets/update_dataset.rs | 7 ++++--- .../endpoints/voice_designer/voices/create_voice.rs | 7 ++++--- .../endpoints/voice_designer/voices/delete_voice.rs | 7 ++++--- .../endpoints/voice_designer/voices/get_voice.rs | 5 +++-- .../voice_designer/voices/list_available_voices.rs | 5 +++-- .../voice_designer/voices/list_voices_by_session.rs | 5 +++-- .../endpoints/voice_designer/voices/list_voices_by_user.rs | 5 +++-- .../endpoints/voice_designer/voices/update_voice.rs | 7 ++++--- .../endpoints/weights/delete/delete_weight_handler.rs | 7 ++++--- .../endpoints/weights/get/get_weight_handler.rs | 5 +++-- .../weights/list/list_available_weights_handler.rs | 5 +++-- .../weights/list/list_featured_weights_handler.rs | 5 +++-- .../endpoints/weights/list/list_pinned_weights_handler.rs | 5 +++-- .../endpoints/weights/list/list_weights_by_user_handler.rs | 5 +++-- .../weights/update/set_model_weight_cover_image_handler.rs | 7 ++++--- .../endpoints/weights/update/update_weight_handler.rs | 7 ++++--- 74 files changed, 259 insertions(+), 188 deletions(-) diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/analytics/log_browser_session_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/analytics/log_browser_session_handler.rs index b6187ff779..29f3460d58 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/analytics/log_browser_session_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/analytics/log_browser_session_handler.rs @@ -2,6 +2,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::{web, HttpRequest, HttpResponse}; use log::warn; @@ -87,9 +88,9 @@ impl fmt::Display for LogBrowserSessionError { )] pub async fn log_browser_session_handler( http_request: HttpRequest, - request: web::Json, + request: Json, server_state: web::Data>, -) -> Result, LogBrowserSessionError> +) -> Result, LogBrowserSessionError> { let mut mysql_connection = server_state.mysql_pool .acquire() @@ -131,6 +132,6 @@ pub async fn log_browser_session_handler( log_token: token, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/create_beta_keys_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/create_beta_keys_handler.rs index 40ddfaf5ed..b9a384188a 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/create_beta_keys_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/create_beta_keys_handler.rs @@ -2,6 +2,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::{web, HttpRequest}; use log::warn; @@ -56,9 +57,9 @@ pub struct CreateBetaKeysSuccessResponse { )] pub async fn create_beta_keys_handler( http_request: HttpRequest, - request: web::Json, + request: Json, server_state: web::Data>, -) -> Result, CommonWebError> +) -> Result, CommonWebError> { let user_session = require_moderator(&http_request, &server_state.session_checker, &server_state.mysql_pool).await?; @@ -106,6 +107,6 @@ pub async fn create_beta_keys_handler( beta_keys, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/edit_beta_key_distributed_flag_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/edit_beta_key_distributed_flag_handler.rs index e5253b9a8f..6915e6be8b 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/edit_beta_key_distributed_flag_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/edit_beta_key_distributed_flag_handler.rs @@ -2,6 +2,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::web::Path; use actix_web::{web, HttpRequest}; @@ -53,10 +54,10 @@ pub struct EditBetaKeyDistributedFlagSuccessResponse { )] pub async fn edit_beta_key_distributed_flag_handler( http_request: HttpRequest, - request: web::Json, + request: Json, path: Path, server_state: web::Data>, -) -> Result, CommonWebError> +) -> Result, CommonWebError> { let user_session = require_moderator(&http_request, &server_state.session_checker, &server_state.mysql_pool).await?; @@ -71,5 +72,5 @@ pub async fn edit_beta_key_distributed_flag_handler( success: true, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/edit_beta_key_note_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/edit_beta_key_note_handler.rs index 67525df93d..c6faf95677 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/edit_beta_key_note_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/edit_beta_key_note_handler.rs @@ -2,6 +2,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::web::Path; use actix_web::{web, HttpRequest}; @@ -52,10 +53,10 @@ pub struct EditBetaKeyNoteSuccessResponse { )] pub async fn edit_beta_key_note_handler( http_request: HttpRequest, - request: web::Json, + request: Json, path: Path, server_state: web::Data>, -) -> Result, CommonWebError> +) -> Result, CommonWebError> { let user_session = require_moderator(&http_request, &server_state.session_checker, &server_state.mysql_pool).await?; @@ -75,5 +76,5 @@ pub async fn edit_beta_key_note_handler( success: true, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/list_beta_keys_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/list_beta_keys_handler.rs index 9d4de4c760..f25af9b861 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/list_beta_keys_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/list_beta_keys_handler.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::web::Query; use actix_web::{web, HttpRequest}; @@ -107,7 +108,7 @@ pub async fn list_beta_keys_handler( http_request: HttpRequest, query: Query, server_state: web::Data> -) -> Result, CommonWebError> { +) -> Result, CommonWebError> { let user_session = require_user_session(&http_request, &server_state.session_checker, &server_state.mysql_pool) .await?; @@ -212,5 +213,5 @@ pub async fn list_beta_keys_handler( } }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/redeem_beta_key_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/redeem_beta_key_handler.rs index e23361b3ea..d8f3ce0438 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/redeem_beta_key_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/redeem_beta_key_handler.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use actix_web::{web, HttpRequest}; +use actix_web::web::Json; use log::warn; use utoipa::ToSchema; @@ -48,9 +49,9 @@ pub struct RedeemBetaKeySuccessResponse { )] pub async fn redeem_beta_key_handler( http_request: HttpRequest, - request: web::Json, + request: Json, server_state: web::Data>, -) -> Result, CommonWebError> +) -> Result, CommonWebError> { let user_session = require_user_session(&http_request, &server_state.session_checker, &server_state.mysql_pool).await?; @@ -90,7 +91,7 @@ pub async fn redeem_beta_key_handler( success: true, }; - Ok(web::Json(response)) + Ok(Json(response)) } async fn enroll_in_studio( diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/comments/create_comment_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/comments/create_comment_handler.rs index 1085449ca9..a603571280 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/comments/create_comment_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/comments/create_comment_handler.rs @@ -7,6 +7,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::{web, HttpRequest, HttpResponse}; use log::warn; @@ -95,9 +96,9 @@ impl fmt::Display for CreateCommentError { )] pub async fn create_comment_handler( http_request: HttpRequest, - request: web::Json, + request: Json, server_state: web::Data>, -) -> Result, CreateCommentError> +) -> Result, CreateCommentError> { // NB(bt,2023-12-14): Kasisnu found that we're getting entity type mismatches in production. Apart from // querying the database for entity existence, this is the next best way to prevent incorrect comment @@ -188,5 +189,5 @@ pub async fn create_comment_handler( comment_token, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/comments/delete_comment_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/comments/delete_comment_handler.rs index ac7fef6681..fe0b4bed73 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/comments/delete_comment_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/comments/delete_comment_handler.rs @@ -7,6 +7,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::web::Path; use actix_web::{web, HttpRequest, HttpResponse}; @@ -89,9 +90,9 @@ impl fmt::Display for DeleteCommentError { pub async fn delete_comment_handler( http_request: HttpRequest, path: Path, - request: web::Json, + request: Json, server_state: web::Data> -) -> Result, DeleteCommentError> { +) -> Result, DeleteCommentError> { let mut mysql_connection = server_state.mysql_pool .acquire() .await @@ -165,5 +166,5 @@ pub async fn delete_comment_handler( } }; - Ok(web::Json(SimpleGenericJsonSuccess { success: true })) + Ok(Json(SimpleGenericJsonSuccess { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/comments/list_comments_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/comments/list_comments_handler.rs index 4b6b4632f7..5f9055212f 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/comments/list_comments_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/comments/list_comments_handler.rs @@ -7,6 +7,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::web::Path; use actix_web::{web, HttpRequest, HttpResponse}; @@ -129,7 +130,7 @@ pub async fn list_comments_handler( _http_request: HttpRequest, path: Path, server_state: web::Data> -) -> Result, ListCommentsError> +) -> Result, ListCommentsError> { let entity_token = CommentEntityToken::from_entity_type_and_token( path.entity_type, &path.entity_token); @@ -174,5 +175,5 @@ pub async fn list_comments_handler( .collect(), }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/download_job/enqueue_generic_download.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/download_job/enqueue_generic_download.rs index 1e8df920e7..cb2e5db237 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/download_job/enqueue_generic_download.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/download_job/enqueue_generic_download.rs @@ -2,6 +2,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::{web, HttpRequest, HttpResponse}; use log::{info, warn}; @@ -69,8 +70,8 @@ impl fmt::Display for EnqueueGenericDownloadError { pub async fn enqueue_generic_download_handler( http_request: HttpRequest, - request: web::Json, - server_state: web::Data>) -> Result, EnqueueGenericDownloadError> + request: Json, + server_state: web::Data>) -> Result, EnqueueGenericDownloadError> { if let Err(_err) = server_state.redis_rate_limiters.model_upload.rate_limit_request(&http_request).await { return Err(EnqueueGenericDownloadError::RateLimited); @@ -150,5 +151,5 @@ pub async fn enqueue_generic_download_handler( job_token: job_token.to_string(), }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/download_job/get_generic_upload_job_status.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/download_job/get_generic_upload_job_status.rs index ab8b2b1a10..ac941555b1 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/download_job/get_generic_upload_job_status.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/download_job/get_generic_upload_job_status.rs @@ -2,6 +2,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::web::Path; use actix_web::{web, HttpMessage, HttpRequest, HttpResponse}; @@ -87,7 +88,7 @@ impl fmt::Display for GetGenericDownloadJobStatusError { pub async fn get_generic_download_job_status_handler( http_request: HttpRequest, path: Path, - server_state: web::Data>) -> Result, GetGenericDownloadJobStatusError> + server_state: web::Data>) -> Result, GetGenericDownloadJobStatusError> { let maybe_job_status = get_generic_download_job_status( &path.token, @@ -142,5 +143,5 @@ pub async fn get_generic_download_job_status_handler( state: model_for_response, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/featured_items/create_featured_item_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/featured_items/create_featured_item_handler.rs index 8012b82527..bd1ad809f2 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/featured_items/create_featured_item_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/featured_items/create_featured_item_handler.rs @@ -2,6 +2,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::{web, HttpRequest}; use log::{error, warn}; @@ -50,9 +51,9 @@ pub struct CreateFeaturedItemSuccessResponse { )] pub async fn create_featured_item_handler( http_request: HttpRequest, - request: web::Json, + request: Json, server_state: web::Data>, -) -> Result, CommonWebError> +) -> Result, CommonWebError> { // NB(bt,2023-12-14): Kasisnu found that we're getting entity type mismatches in production. Apart from // querying the database for entity existence, this is the next best way to prevent incorrect comment @@ -163,5 +164,5 @@ pub async fn create_featured_item_handler( success: true, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/featured_items/delete_featured_item_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/featured_items/delete_featured_item_handler.rs index 93e998e8b5..3ee1a21e66 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/featured_items/delete_featured_item_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/featured_items/delete_featured_item_handler.rs @@ -2,6 +2,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::{web, HttpRequest}; use log::{error, warn}; @@ -46,9 +47,9 @@ pub struct DeleteFeaturedItemRequest { )] pub async fn delete_featured_item_handler( http_request: HttpRequest, - request: web::Json, + request: Json, server_state: web::Data> -) -> Result, CommonWebError> { +) -> Result, CommonWebError> { // NB(bt,2023-12-14): Kasisnu found that we're getting entity type mismatches in production. Apart from // querying the database for entity existence, this is the next best way to prevent incorrect comment @@ -137,5 +138,5 @@ pub async fn delete_featured_item_handler( } }; - Ok(web::Json(SimpleGenericJsonSuccess { success: true })) + Ok(Json(SimpleGenericJsonSuccess { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/inference_job/admin/kill_inference_jobs_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/inference_job/admin/kill_inference_jobs_handler.rs index ce33ea2646..07c8f2afa2 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/inference_job/admin/kill_inference_jobs_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/inference_job/admin/kill_inference_jobs_handler.rs @@ -3,6 +3,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::{web, HttpRequest}; use log::warn; @@ -52,8 +53,8 @@ pub struct KillInferenceJobsResponse { pub async fn kill_generic_inference_jobs_handler( http_request: HttpRequest, - request: web::Json, - server_state: web::Data>) -> Result, CommonWebError> + request: Json, + server_state: web::Data>) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -106,5 +107,5 @@ pub async fn kill_generic_inference_jobs_handler( success: true, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/inference_job/delete/terminate_inference_job_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/inference_job/delete/terminate_inference_job_handler.rs index e04f572f44..e8776603a7 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/inference_job/delete/terminate_inference_job_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/inference_job/delete/terminate_inference_job_handler.rs @@ -2,6 +2,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::web::Path; use actix_web::{web, HttpMessage, HttpRequest}; @@ -47,7 +48,7 @@ pub struct TerminateInferenceJobSuccessResponse { pub async fn terminate_inference_job_handler( http_request: HttpRequest, path: Path, - server_state: web::Data>) -> Result, CommonWebError> + server_state: web::Data>) -> Result, CommonWebError> { let mut mysql_connection = server_state.mysql_pool.acquire() .await @@ -105,5 +106,5 @@ pub async fn terminate_inference_job_handler( success: true, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/change_media_file_visibility_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/change_media_file_visibility_handler.rs index 5ad95dc0c4..5dae20013f 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/change_media_file_visibility_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/change_media_file_visibility_handler.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use actix_web::web::Path; +use actix_web::web::Json; use actix_web::{web, HttpRequest}; use log::warn; use utoipa::ToSchema; @@ -43,8 +44,8 @@ pub struct ChangeMediaFileVisibilityRequest { pub async fn change_media_file_visibility_handler( http_request: HttpRequest, path: Path, - request: web::Json, - server_state: web::Data>) -> Result, CommonWebError> + request: Json, + server_state: web::Data>) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -123,5 +124,5 @@ pub async fn change_media_file_visibility_handler( } }; - Ok(web::Json(SimpleGenericJsonSuccess { success: true })) + Ok(Json(SimpleGenericJsonSuccess { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/rename_media_file_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/rename_media_file_handler.rs index fab1485dda..4f10688ab4 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/rename_media_file_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/rename_media_file_handler.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use actix_web::web::Path; +use actix_web::web::Json; use actix_web::{web, HttpRequest}; use log::warn; use utoipa::ToSchema; @@ -42,9 +43,9 @@ pub struct RenameMediaFileRequest { pub async fn rename_media_file_handler( http_request: HttpRequest, path: Path, - request: web::Json, + request: Json, server_state: web::Data> -) -> Result, CommonWebError>{ +) -> Result, CommonWebError>{ let maybe_user_session = server_state .session_checker .maybe_get_user_session(&http_request, &server_state.mysql_pool) @@ -99,5 +100,5 @@ pub async fn rename_media_file_handler( CommonWebError::from_anyhow_error(err) })?; - Ok(web::Json(SimpleGenericJsonSuccess { success: true })) + Ok(Json(SimpleGenericJsonSuccess { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/set_media_file_cover_image_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/set_media_file_cover_image_handler.rs index c8445edc77..8a8c75774a 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/set_media_file_cover_image_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/set_media_file_cover_image_handler.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use actix_web::web::Path; +use actix_web::web::Json; use actix_web::{web, HttpRequest}; use log::{info, warn}; use utoipa::ToSchema; @@ -48,9 +49,9 @@ pub struct SetMediaFileCoverImageRequest { pub async fn set_media_file_cover_image_handler( http_request: HttpRequest, path: Path, - request: web::Json, + request: Json, server_state: web::Data> -) -> Result, CommonWebError>{ +) -> Result, CommonWebError>{ let maybe_user_session = server_state .session_checker .maybe_get_user_session(&http_request, &server_state.mysql_pool) @@ -152,5 +153,5 @@ pub async fn set_media_file_cover_image_handler( } }; - Ok(web::Json(SimpleGenericJsonSuccess { success: true })) + Ok(Json(SimpleGenericJsonSuccess { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/update_media_file_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/update_media_file_handler.rs index e183a44d37..e4054e8361 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/update_media_file_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/update_media_file_handler.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use actix_web::web::Path; +use actix_web::web::Json; use actix_web::{web, HttpRequest}; use log::warn; @@ -36,8 +37,8 @@ pub struct UpdateMediaFilePathInfo { pub async fn update_media_file_handler( http_request: HttpRequest, path: Path, - request: web::Json, - server_state: web::Data>) -> Result, CommonWebError> + request: Json, + server_state: web::Data>) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -116,5 +117,5 @@ pub async fn update_media_file_handler( } }; - Ok(web::Json(UpdateMediaFileResponse { success: true })) + Ok(Json(UpdateMediaFileResponse { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/info/moderator_token_info_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/info/moderator_token_info_handler.rs index 8ac66ae171..8479d0cbf3 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/info/moderator_token_info_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/info/moderator_token_info_handler.rs @@ -2,6 +2,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::web::Path; use actix_web::{web, HttpRequest}; @@ -58,7 +59,7 @@ pub async fn moderator_get_token_info_handler( path: Path, http_request: HttpRequest, server_state: web::Data> -) -> Result, CommonWebError> { +) -> Result, CommonWebError> { let user_session = require_moderator(&http_request, &server_state.session_checker, &server_state.mysql_pool).await?; @@ -75,7 +76,7 @@ pub async fn moderator_get_token_info_handler( maybe_payload: maybe_result, }; - Ok(web::Json(response)) + Ok(Json(response)) } const LEGACY_USER_TOKEN_PREFIX : &str = "U:"; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/add_ip_ban.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/add_ip_ban.rs index 4f087258d5..777f33fac4 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/add_ip_ban.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/add_ip_ban.rs @@ -2,6 +2,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::{web, HttpRequest}; use log::{info, warn}; @@ -23,9 +24,9 @@ pub struct AddIpBanRequest { // NB: Not using derive_more::Display since Clion doesn't understand it. pub async fn add_ip_ban_handler( http_request: HttpRequest, - request: web::Json, + request: Json, server_state: web::Data> -) -> Result, CommonWebError> { +) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -70,5 +71,5 @@ pub async fn add_ip_ban_handler( CommonWebError::from_anyhow_error(err) })?; - Ok(web::Json(SimpleGenericJsonSuccess { success: true })) + Ok(Json(SimpleGenericJsonSuccess { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/delete_ip_ban.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/delete_ip_ban.rs index 47a46afc5a..7b64f235bb 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/delete_ip_ban.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/delete_ip_ban.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::web::Path; use actix_web::{web, HttpRequest}; @@ -27,9 +28,9 @@ pub struct DeleteIpBanRequest { pub async fn delete_ip_ban_handler( http_request: HttpRequest, path: Path, - request: web::Json, + request: Json, server_state: web::Data> -) -> Result, CommonWebError> { +) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -77,5 +78,5 @@ pub async fn delete_ip_ban_handler( } }; - Ok(web::Json(SimpleGenericJsonSuccess { success: true })) + Ok(Json(SimpleGenericJsonSuccess { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/get_ip_ban.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/get_ip_ban.rs index 521db56d99..c188fca429 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/get_ip_ban.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/get_ip_ban.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::web::Path; use actix_web::{web, HttpRequest}; @@ -42,7 +43,7 @@ pub async fn get_ip_ban_handler( http_request: HttpRequest, path: Path, server_state: web::Data> -) -> Result, CommonWebError> { +) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -97,5 +98,5 @@ pub async fn get_ip_ban_handler( ip_address_ban: result, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/list_ip_bans.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/list_ip_bans.rs index c63c3b4505..b50f259970 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/list_ip_bans.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/ip_bans/list_ip_bans.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::{web, HttpRequest}; use chrono::{DateTime, Utc}; @@ -34,7 +35,7 @@ pub struct IpBanRecordForList { pub async fn list_ip_bans_handler( http_request: HttpRequest, server_state: web::Data> -) -> Result, CommonWebError> { +) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -85,5 +86,5 @@ pub async fn list_ip_bans_handler( ip_address_bans: results, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/get_tts_inference_queue_count.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/get_tts_inference_queue_count.rs index dc4d39df3f..0a3665ecf9 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/get_tts_inference_queue_count.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/get_tts_inference_queue_count.rs @@ -2,6 +2,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::{web, HttpRequest}; use log::warn; @@ -34,7 +35,7 @@ pub struct GetTtsInferenceQueueCountResponse { pub async fn get_tts_inference_queue_count_handler( http_request: HttpRequest, server_state: web::Data> -) -> Result, CommonWebError> { +) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -85,5 +86,5 @@ pub async fn get_tts_inference_queue_count_handler( attempt_failed_count: result.attempt_failed_count, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/get_w2l_inference_queue_count.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/get_w2l_inference_queue_count.rs index 149994eeda..52b49308a8 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/get_w2l_inference_queue_count.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/get_w2l_inference_queue_count.rs @@ -47,7 +47,7 @@ impl fmt::Display for GetW2lInferenceQueueCountError { pub async fn get_w2l_inference_queue_count_handler( http_request: HttpRequest, server_state: web::Data> -) -> Result, GetW2lInferenceQueueCountError> { +) -> Result, GetW2lInferenceQueueCountError> { let maybe_user_session = server_state .session_checker @@ -92,5 +92,5 @@ pub async fn get_w2l_inference_queue_count_handler( seconds_since_first: result.seconds_since_first, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/kill_tts_inference_jobs.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/kill_tts_inference_jobs.rs index ab81e8ab06..e33073c950 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/kill_tts_inference_jobs.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/kill_tts_inference_jobs.rs @@ -2,6 +2,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::{web, HttpRequest}; use log::{error, warn}; @@ -45,8 +46,8 @@ pub struct KillTtsInferenceJobsResponse { pub async fn kill_tts_inference_jobs_handler( http_request: HttpRequest, - request: web::Json, - server_state: web::Data>) -> Result, CommonWebError> + request: Json, + server_state: web::Data>) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -88,5 +89,5 @@ pub async fn kill_tts_inference_jobs_handler( success: true, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_feature_flags/moderator_edit_user_feature_flags_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_feature_flags/moderator_edit_user_feature_flags_handler.rs index e22e076df1..80508693f5 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_feature_flags/moderator_edit_user_feature_flags_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_feature_flags/moderator_edit_user_feature_flags_handler.rs @@ -84,7 +84,7 @@ pub async fn moderator_edit_user_feature_flags_handler( request: Json, server_state: Data>, redis_pool: Data>, -) -> Result, CommonWebError> { +) -> Result, CommonWebError> { let user_session = require_moderator(&http_request, &server_state.session_checker, &server_state.mysql_pool).await?; @@ -189,5 +189,5 @@ pub async fn moderator_edit_user_feature_flags_handler( let _r: Result, _> = redis.del(&cache_key); } - Ok(web::Json(SimpleGenericJsonSuccess { success: true })) + Ok(Json(SimpleGenericJsonSuccess { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/subscriptions/unsubscribe_reason.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/subscriptions/unsubscribe_reason.rs index d33a6c39b6..a93decf00d 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/subscriptions/unsubscribe_reason.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/subscriptions/unsubscribe_reason.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use actix_web::http::StatusCode; +use actix_web::web::Json; use actix_web::{web, HttpRequest}; use log::{error, info}; @@ -31,8 +32,8 @@ pub struct SetUnsubscribeReasonResponse { pub async fn set_unsubscribe_reason_handler( http_request: HttpRequest, - request: web::Json, - server_state: web::Data>) -> Result, CommonWebError> + request: Json, + server_state: web::Data>) -> Result, CommonWebError> { let mut mysql_connection = server_state.mysql_pool.acquire() .await @@ -73,5 +74,5 @@ pub async fn set_unsubscribe_reason_handler( success: true, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/trending/list_trending_tts_models.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/trending/list_trending_tts_models.rs index 8312f53d5b..2f10fdef17 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/trending/list_trending_tts_models.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/trending/list_trending_tts_models.rs @@ -12,6 +12,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::{web, HttpRequest}; use log::error; @@ -51,7 +52,7 @@ pub type WindowTrends = HashMap>; pub async fn list_trending_tts_models_handler( _http_request: HttpRequest, - server_state: web::Data>) -> Result, CommonWebError> + server_state: web::Data>) -> Result, CommonWebError> { // TODO: Cache the outputs! @@ -112,5 +113,5 @@ pub async fn list_trending_tts_models_handler( top_trending_by_language_code, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/delete_tts_model.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/delete_tts_model.rs index a4559816d5..11d8cd0b18 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/delete_tts_model.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/delete_tts_model.rs @@ -2,6 +2,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::web::Path; use actix_web::{web, HttpRequest}; @@ -37,9 +38,9 @@ pub struct DeleteTtsModelRequest { pub async fn delete_tts_model_handler( http_request: HttpRequest, path: Path, - request: web::Json, + request: Json, server_state: web::Data> -) -> Result, CommonWebError> { +) -> Result, CommonWebError> { // NB: Disable if we've migrated to model_weights if server_state.flags.switch_tts_to_model_weights { warn!("Migration to model_weights for tts. Cannot delete old model."); @@ -154,5 +155,5 @@ pub async fn delete_tts_model_handler( } }; - Ok(web::Json(SimpleGenericJsonSuccess { success: true })) + Ok(Json(SimpleGenericJsonSuccess { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/delete_tts_result.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/delete_tts_result.rs index d71b6f1907..b78f281059 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/delete_tts_result.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/delete_tts_result.rs @@ -2,6 +2,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::web::Path; use actix_web::{web, HttpRequest}; @@ -36,9 +37,9 @@ pub struct DeleteTtsInferenceResultRequest { pub async fn delete_tts_inference_result_handler( http_request: HttpRequest, path: Path, - request: web::Json, + request: Json, server_state: web::Data> -) -> Result, CommonWebError> { +) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker .maybe_get_user_session(&http_request, &server_state.mysql_pool) @@ -141,5 +142,5 @@ pub async fn delete_tts_inference_result_handler( } }; - Ok(web::Json(SimpleGenericJsonSuccess { success: true })) + Ok(Json(SimpleGenericJsonSuccess { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/edit_tts_model.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/edit_tts_model.rs index 44ab796897..556d8787a2 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/edit_tts_model.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/edit_tts_model.rs @@ -92,7 +92,7 @@ pub async fn edit_tts_model_handler( http_request: HttpRequest, path: Path, request: Json, - server_state: web::Data>) -> Result, CommonWebError> + server_state: web::Data>) -> Result, CommonWebError> { // NB: Disable if we've migrated to model_weights if server_state.flags.switch_tts_to_model_weights { @@ -300,7 +300,7 @@ pub async fn edit_tts_model_handler( let _r = redis_ttl_cache.delete_from_cache(&cache_key).ok(); } - Ok(web::Json(SimpleGenericJsonSuccess { success: true })) + Ok(Json(SimpleGenericJsonSuccess { success: true })) } async fn update_mod_details( diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/edit_tts_result.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/edit_tts_result.rs index abecf5900b..63c8cd46d7 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/edit_tts_result.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/edit_tts_result.rs @@ -6,6 +6,7 @@ use std::sync::Arc; use actix_web::web::Path; +use actix_web::web::Json; use actix_web::{web, HttpRequest}; use log::{error, warn}; @@ -33,8 +34,8 @@ pub struct EditTtsResultRequest { pub async fn edit_tts_inference_result_handler( http_request: HttpRequest, path: Path, - request: web::Json, - server_state: web::Data>) -> Result, CommonWebError> + request: Json, + server_state: web::Data>) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -120,5 +121,5 @@ pub async fn edit_tts_inference_result_handler( CommonWebError::from_anyhow_error(err) })?; - Ok(web::Json(SimpleGenericJsonSuccess { success: true })) + Ok(Json(SimpleGenericJsonSuccess { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_upload_tts_model.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_upload_tts_model.rs index 6dc76ba192..5fea2804ba 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_upload_tts_model.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_upload_tts_model.rs @@ -6,6 +6,7 @@ use std::sync::Arc; use actix_web::{web, HttpRequest}; +use actix_web::web::Json; use log::warn; use crate::http_server::validations::validate_model_title::validate_model_title; @@ -69,9 +70,9 @@ pub struct UploadTtsModelSuccessResponse { // NB: Not using derive_more::Display since Clion doesn't understand it. pub async fn upload_tts_model_handler( http_request: HttpRequest, - request: web::Json, + request: Json, server_state: web::Data> -) -> Result, CommonWebError> { +) -> Result, CommonWebError> { if server_state.flags.disable_tts { return Err(CommonWebError::TooManyRequests); @@ -186,7 +187,7 @@ pub async fn upload_tts_model_handler( CommonWebError::from_anyhow_error(e) })?; - Ok(web::Json(UploadTtsModelSuccessResponse { + Ok(Json(UploadTtsModelSuccessResponse { success: true, job_token: job_token.to_string(), job_type: download_job_type, diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_inference_job_status.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_inference_job_status.rs index 50a4a724f1..2010564d67 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_inference_job_status.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_inference_job_status.rs @@ -2,6 +2,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::web::Path; use actix_web::{web, HttpMessage, HttpRequest}; @@ -61,7 +62,7 @@ pub struct GetTtsInferenceStatusSuccessResponse { pub async fn get_tts_inference_job_status_handler( http_request: HttpRequest, path: Path, - server_state: web::Data>) -> Result, CommonWebError> + server_state: web::Data>) -> Result, CommonWebError> { let job_token = path.into_inner().token; @@ -80,7 +81,7 @@ pub async fn get_tts_inference_job_status_handler( legacy_lookup(&job_token, &server_state).await? }; - Ok(web::Json(GetTtsInferenceStatusSuccessResponse { + Ok(Json(GetTtsInferenceStatusSuccessResponse { success: true, state: record_for_response, })) diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_model.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_model.rs index 01c5d7e670..ba6650738f 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_model.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_model.rs @@ -6,6 +6,7 @@ use std::sync::Arc; use actix_web::web::Path; +use actix_web::web::Json; use actix_web::{web, HttpRequest}; use chrono::{DateTime, Utc}; use log::warn; @@ -152,7 +153,7 @@ pub struct TtsModelModeratorFieldInfo { pub async fn get_tts_model_handler( http_request: HttpRequest, path: Path, - server_state: web::Data>) -> Result, CommonWebError> + server_state: web::Data>) -> Result, CommonWebError> { let mut mysql_connection = server_state.mysql_pool.acquire() .await @@ -327,5 +328,5 @@ pub async fn get_tts_model_handler( }, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_model_use_count.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_model_use_count.rs index d6d0bc2030..faf0929b8a 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_model_use_count.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_model_use_count.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::web::Path; use actix_web::{web, HttpMessage, HttpRequest}; @@ -28,7 +29,7 @@ pub struct GetTtsModelUseCountSuccessResponse { pub async fn get_tts_model_use_count_handler( http_request: HttpRequest, path: Path, - server_state: web::Data>) -> Result, CommonWebError> + server_state: web::Data>) -> Result, CommonWebError> { let mut redis = server_state.redis_pool .get() @@ -45,7 +46,7 @@ pub async fn get_tts_model_use_count_handler( CommonWebError::from_error(e) })?; - Ok(web::Json(GetTtsModelUseCountSuccessResponse { + Ok(Json(GetTtsModelUseCountSuccessResponse { success: true, count, })) diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_result.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_result.rs index 277989210a..d598327b61 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_result.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_result.rs @@ -2,6 +2,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::web::Path; use actix_web::{web, HttpMessage, HttpRequest}; @@ -29,7 +30,7 @@ pub struct GetTtsResultSuccessResponse { pub async fn get_tts_inference_result_handler( http_request: HttpRequest, path: Path, - server_state: web::Data>) -> Result, CommonWebError> + server_state: web::Data>) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -80,7 +81,7 @@ pub async fn get_tts_inference_result_handler( inference_result.maybe_moderator_fields = None; } - Ok(web::Json(GetTtsResultSuccessResponse { + Ok(Json(GetTtsResultSuccessResponse { success: true, result: inference_result, })) diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_upload_model_job_status.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_upload_model_job_status.rs index 839b1dd741..5e5cd83829 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_upload_model_job_status.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_upload_model_job_status.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::web::Path; use actix_web::{web, HttpMessage, HttpRequest}; @@ -49,7 +50,7 @@ pub struct GetTtsUploadModelStatusSuccessResponse { pub async fn get_tts_upload_model_job_status_handler( http_request: HttpRequest, path: Path, - server_state: web::Data>) -> Result, CommonWebError> + server_state: web::Data>) -> Result, CommonWebError> { // NB: Since this is publicly exposed, we don't query sensitive data. let record = get_tts_model_upload_job_status(&path.token, &server_state.mysql_pool) @@ -92,7 +93,7 @@ pub async fn get_tts_upload_model_job_status_handler( updated_at: record.updated_at, }; - Ok(web::Json(GetTtsUploadModelStatusSuccessResponse { + Ok(Json(GetTtsUploadModelStatusSuccessResponse { success: true, state: model_for_response, })) diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/search_tts_models_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/search_tts_models_handler.rs index a733c18901..aaf853a07f 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/search_tts_models_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/search_tts_models_handler.rs @@ -7,6 +7,7 @@ use std::collections::HashSet; use std::sync::Arc; use actix_web::{web, HttpRequest}; +use actix_web::web::Json; use chrono::{DateTime, Utc}; use log::error; @@ -66,8 +67,8 @@ pub struct SearchTtsModelsSuccessResponse { // NB: Not using derive_more::Display since Clion doesn't understand it. pub async fn search_tts_models_handler( _http_request: HttpRequest, - request: web::Json, - server_state: web::Data>) -> Result, CommonWebError> + request: Json, + server_state: web::Data>) -> Result, CommonWebError> { let results = search_tts_models( &server_state.elasticsearch, @@ -109,7 +110,7 @@ pub async fn search_tts_models_handler( new_results.push(result); } - Ok(web::Json(SearchTtsModelsSuccessResponse { + Ok(Json(SearchTtsModelsSuccessResponse { success: true, models: new_results, })) diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/create_user_bookmark_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/create_user_bookmark_handler.rs index 2142681acd..c672ae0df3 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/create_user_bookmark_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/create_user_bookmark_handler.rs @@ -7,6 +7,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::{web, HttpRequest, HttpResponse}; use log::{error, warn}; @@ -95,9 +96,9 @@ impl fmt::Display for CreateUserBookmarkError { )] pub async fn create_user_bookmark_handler( http_request: HttpRequest, - request: web::Json, + request: Json, server_state: web::Data>, -) -> Result, CreateUserBookmarkError> +) -> Result, CreateUserBookmarkError> { // NB(bt,2023-12-14): Kasisnu found that we're getting entity type mismatches in production. Apart from // querying the database for entity existence, this is the next best way to prevent incorrect comment @@ -226,5 +227,5 @@ pub async fn create_user_bookmark_handler( new_bookmark_count_for_entity: count.total_count, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/delete_user_bookmark_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/delete_user_bookmark_handler.rs index e7287756e3..b2452ab74c 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/delete_user_bookmark_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/delete_user_bookmark_handler.rs @@ -7,6 +7,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::web::Path; use actix_web::{web, HttpRequest, HttpResponse}; @@ -91,9 +92,9 @@ impl fmt::Display for DeleteUserBookmarkError { pub async fn delete_user_bookmark_handler( http_request: HttpRequest, path: Path, - _request: web::Json, + _request: Json, server_state: web::Data> -) -> Result, DeleteUserBookmarkError> { +) -> Result, DeleteUserBookmarkError> { let mut mysql_connection = server_state.mysql_pool .acquire() .await @@ -180,5 +181,5 @@ pub async fn delete_user_bookmark_handler( } }; - Ok(web::Json(SimpleGenericJsonSuccess { success: true })) + Ok(Json(SimpleGenericJsonSuccess { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/list_user_bookmarks_for_entity_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/list_user_bookmarks_for_entity_handler.rs index cb78d8714c..bc1cd339b1 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/list_user_bookmarks_for_entity_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/list_user_bookmarks_for_entity_handler.rs @@ -7,6 +7,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::web::Path; use actix_web::{web, HttpRequest, HttpResponse}; @@ -92,7 +93,7 @@ pub async fn list_user_bookmarks_for_entity_handler( _http_request: HttpRequest, path: Path, server_state: web::Data> -) -> Result, ListUserBookmarksForEntityError> +) -> Result, ListUserBookmarksForEntityError> { let entity_token = UserBookmarkEntityToken::from_entity_type_and_token( path.entity_type, &path.entity_token); @@ -128,5 +129,5 @@ pub async fn list_user_bookmarks_for_entity_handler( .collect(), }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/list_user_bookmarks_for_session_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/list_user_bookmarks_for_session_handler.rs index 3c4620d04e..6fb456b4a5 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/list_user_bookmarks_for_session_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/list_user_bookmarks_for_session_handler.rs @@ -2,6 +2,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::web::Query; use actix_web::{web, HttpRequest, HttpResponse}; @@ -84,7 +85,7 @@ pub async fn list_user_bookmarks_for_session_handler( http_request: HttpRequest, query: Query, server_state: web::Data> -) -> Result, ListUserBookmarksError> +) -> Result, ListUserBookmarksError> { let user_session = server_state.session_checker .maybe_get_user_session(&http_request, &server_state.mysql_pool) @@ -108,5 +109,5 @@ pub async fn list_user_bookmarks_for_session_handler( user_bookmarks: Vec::new() }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/user_ratings/get_user_rating_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/user_ratings/get_user_rating_handler.rs index 9060323c2c..99c7a05aa2 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/user_ratings/get_user_rating_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/user_ratings/get_user_rating_handler.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use actix_web::{web, HttpRequest}; +use actix_web::web::Json; use log::{error, info}; use utoipa::ToSchema; @@ -62,7 +63,7 @@ pub struct GetUserRatingResponse { pub async fn get_user_rating_handler( http_request: HttpRequest, path: web::Path, - server_state: web::Data>) -> Result, CommonWebError> + server_state: web::Data>) -> Result, CommonWebError> { let mut mysql_connection = server_state.mysql_pool.acquire() .await @@ -119,5 +120,5 @@ pub async fn get_user_rating_handler( maybe_rating_value: maybe_rating.map(|rating| rating.rating_value), }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/user_ratings/set_user_rating_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/user_ratings/set_user_rating_handler.rs index 0abf554668..22eb512319 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/user_ratings/set_user_rating_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/user_ratings/set_user_rating_handler.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use actix_web::{web, HttpRequest}; +use actix_web::web::Json; use log::{error, info, warn}; use sqlx::Acquire; use utoipa::ToSchema; @@ -67,8 +68,8 @@ pub struct SetUserRatingResponse { )] pub async fn set_user_rating_handler( http_request: HttpRequest, - request: web::Json, - server_state: web::Data>) -> Result, CommonWebError> + request: Json, + server_state: web::Data>) -> Result, CommonWebError> { // NB(bt,2023-12-14): Kasisnu found that we're getting entity type mismatches in production. Apart from // querying the database for entity existence, this is the next best way to prevent incorrect comment @@ -237,5 +238,5 @@ pub async fn set_user_rating_handler( new_positive_rating_count_for_entity: count.positive_count, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/users/edit_profile_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/users/edit_profile_handler.rs index 2a60d2f873..6c7a63a675 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/users/edit_profile_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/users/edit_profile_handler.rs @@ -5,6 +5,7 @@ use actix_web::web::Path; +use actix_web::web::Json; use actix_web::{web, HttpRequest}; use log::warn; use sqlx::MySqlPool; @@ -61,11 +62,11 @@ pub struct EditProfileSuccessResponse { pub async fn edit_profile_handler( http_request: HttpRequest, path: Path, - request: web::Json, + request: Json, mysql_pool: web::Data, redis_ttl_cache: web::Data, session_checker: web::Data, -) -> Result, CommonWebError> +) -> Result, CommonWebError> { let maybe_user_session = session_checker .maybe_get_user_session(&http_request, &mysql_pool) @@ -285,5 +286,5 @@ pub async fn edit_profile_handler( success: true, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_clone_requests/check_if_voice_clone_request_submitted.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_clone_requests/check_if_voice_clone_request_submitted.rs index 10192e2b9f..e95b451d42 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_clone_requests/check_if_voice_clone_request_submitted.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_clone_requests/check_if_voice_clone_request_submitted.rs @@ -2,6 +2,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::{web, HttpRequest}; use log::warn; @@ -35,8 +36,8 @@ pub struct CheckIfVoiceRequestSubmittedResponse { pub async fn check_if_voice_clone_request_submitted_handler( http_request: HttpRequest, - request: web::Json, - server_state: web::Data>) -> Result, CommonWebError> + request: Json, + server_state: web::Data>) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -83,5 +84,5 @@ pub async fn check_if_voice_clone_request_submitted_handler( has_submitted, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_clone_requests/create_voice_clone_request.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_clone_requests/create_voice_clone_request.rs index 441acea828..7ccca23953 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_clone_requests/create_voice_clone_request.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_clone_requests/create_voice_clone_request.rs @@ -2,6 +2,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::{web, HttpRequest}; use log::warn; @@ -64,8 +65,8 @@ pub struct CreateVoiceRequestResponse { pub async fn create_voice_clone_request_handler( http_request: HttpRequest, - request: web::Json, - server_state: web::Data>) -> Result, CommonWebError> + request: Json, + server_state: web::Data>) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -113,5 +114,5 @@ pub async fn create_voice_clone_request_handler( success: true, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/inference/enqueue_tts_request.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/inference/enqueue_tts_request.rs index 68353d1cbe..9418e37c1b 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/inference/enqueue_tts_request.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/inference/enqueue_tts_request.rs @@ -5,6 +5,7 @@ use std::sync::Arc; use actix_web::{web, HttpRequest}; +use actix_web::web::Json; use log::warn; use serde::Deserialize; use serde::Serialize; @@ -76,9 +77,9 @@ pub struct EnqueueTTSRequestSuccessResponse { )] pub async fn enqueue_tts_request( http_request: HttpRequest, - request: web::Json, + request: Json, server_state: web::Data> -) -> Result, CommonWebError> { +) -> Result, CommonWebError> { let mut maybe_user_token: Option = None; @@ -194,5 +195,5 @@ pub async fn enqueue_tts_request( inference_job_token: job_token, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_dataset_samples/delete_sample.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_dataset_samples/delete_sample.rs index 21bbda98a1..3e874933ab 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_dataset_samples/delete_sample.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_dataset_samples/delete_sample.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use actix_web::web::Path; +use actix_web::web::Json; use actix_web::{web, HttpRequest}; use log::warn; @@ -31,9 +32,9 @@ pub struct DeleteSampleRequest { pub async fn delete_sample_handler( http_request: HttpRequest, path: Path, - request: web::Json, + request: Json, server_state: web::Data> -) -> Result, CommonWebError> { +) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -120,5 +121,5 @@ pub async fn delete_sample_handler( } }; - Ok(web::Json(SimpleGenericJsonSuccess { success: true })) + Ok(Json(SimpleGenericJsonSuccess { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_dataset_samples/list_samples_by_dataset.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_dataset_samples/list_samples_by_dataset.rs index b20aaedc5d..e7307f16ec 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_dataset_samples/list_samples_by_dataset.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_dataset_samples/list_samples_by_dataset.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use actix_web::{web, HttpRequest}; +use actix_web::web::Json; use chrono::{DateTime, Utc}; use log::warn; @@ -45,7 +46,7 @@ pub async fn list_samples_by_dataset_handler( http_request: HttpRequest, path: web::Path, server_state: web::Data> -) -> Result, CommonWebError> { +) -> Result, CommonWebError> { let maybe_user_session = server_state.session_checker.maybe_get_user_session( &http_request, &server_state.mysql_pool @@ -106,5 +107,5 @@ pub async fn list_samples_by_dataset_handler( samples }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/create_dataset.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/create_dataset.rs index c6dfe6e254..2b35708f54 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/create_dataset.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/create_dataset.rs @@ -2,6 +2,7 @@ use std::fmt; use std::sync::Arc; use actix_web::{web, HttpRequest}; +use actix_web::web::Json; use log::{error, warn}; use enums::common::visibility::Visibility; @@ -31,7 +32,7 @@ pub struct CreateDatasetResponse { // NB: Not using DeriveMore since Clion doesn't understand it. // =============== Handler =============== -pub async fn create_dataset_handler(http_request: HttpRequest, request: web::Json, server_state: web::Data>) -> Result, CommonWebError> { +pub async fn create_dataset_handler(http_request: HttpRequest, request: Json, server_state: web::Data>) -> Result, CommonWebError> { let maybe_user_session = server_state.session_checker.maybe_get_user_session(&http_request, &server_state.mysql_pool).await.map_err(|e| { error!("Error getting user session: {:?}", e); CommonWebError::from_error(e) @@ -75,5 +76,5 @@ pub async fn create_dataset_handler(http_request: HttpRequest, request: web::Jso }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/delete_dataset.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/delete_dataset.rs index 0bae688f3c..8801086155 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/delete_dataset.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/delete_dataset.rs @@ -2,6 +2,7 @@ use std::fmt; use std::sync::Arc; use actix_web::web::Path; +use actix_web::web::Json; use actix_web::{web, HttpRequest}; use log::warn; @@ -34,9 +35,9 @@ pub struct DeleteDatasetPathInfo { pub async fn delete_dataset_handler( http_request: HttpRequest, path: Path, - request: web::Json, + request: Json, server_state: web::Data> -) -> Result, CommonWebError>{ +) -> Result, CommonWebError>{ let maybe_user_session = server_state .session_checker .maybe_get_user_session(&http_request, &server_state.mysql_pool) @@ -137,6 +138,6 @@ pub async fn delete_dataset_handler( } }; - Ok(web::Json(SimpleGenericJsonSuccess { success: true })) + Ok(Json(SimpleGenericJsonSuccess { success: true })) } \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/get_dataset.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/get_dataset.rs index b3be160233..6fb674b6bf 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/get_dataset.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/get_dataset.rs @@ -2,6 +2,7 @@ use std::fmt; use std::sync::Arc; use actix_web::web::Path; +use actix_web::web::Json; use actix_web::{web, HttpRequest}; use chrono::{DateTime, Utc}; use log::warn; @@ -40,7 +41,7 @@ pub async fn get_dataset_handler( http_request: HttpRequest, path: Path, server_state: web::Data> -) -> Result, CommonWebError> { +) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -107,5 +108,5 @@ pub async fn get_dataset_handler( updated_at: dataset.updated_at, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/list_datasets_by_session.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/list_datasets_by_session.rs index 1e7146dae4..353a41b139 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/list_datasets_by_session.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/list_datasets_by_session.rs @@ -2,6 +2,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::{web, HttpRequest}; use chrono::{DateTime, Utc}; @@ -61,7 +62,7 @@ impl ResponseError for ListDatasetsByUserError { pub async fn list_datasets_by_session_handler( http_request: HttpRequest, server_state: web::Data> -) -> Result, ListDatasetsByUserError> { +) -> Result, ListDatasetsByUserError> { let user_session = server_state .session_checker @@ -122,5 +123,5 @@ pub async fn list_datasets_by_session_handler( datasets, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/list_datasets_by_user.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/list_datasets_by_user.rs index cbf51672da..f2c1352bab 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/list_datasets_by_user.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/list_datasets_by_user.rs @@ -2,6 +2,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::web::Path; use actix_web::{web, HttpRequest}; @@ -82,7 +83,7 @@ pub async fn list_datasets_by_user_handler( http_request: HttpRequest, path: Path, server_state: web::Data> -) -> Result, ListDatasetsByUserError> { +) -> Result, ListDatasetsByUserError> { let maybe_user_session = server_state .session_checker @@ -148,5 +149,5 @@ pub async fn list_datasets_by_user_handler( datasets, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/update_dataset.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/update_dataset.rs index cfe2a62cbb..bd533c7745 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/update_dataset.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_datasets/update_dataset.rs @@ -2,6 +2,7 @@ use std::fmt; use std::sync::Arc; use actix_web::web::Path; +use actix_web::web::Json; use actix_web::{web, HttpRequest}; use log::{error, warn}; @@ -45,8 +46,8 @@ pub struct UpdateDatasetPathInfo { pub async fn update_dataset_handler( http_request: HttpRequest, path: Path, - request: web::Json, - server_state: web::Data>) -> Result, CommonWebError> + request: Json, + server_state: web::Data>) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -166,5 +167,5 @@ pub async fn update_dataset_handler( } }; - Ok(web::Json(SimpleGenericJsonSuccess { success: true })) + Ok(Json(SimpleGenericJsonSuccess { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/create_voice.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/create_voice.rs index 539dbf52e8..c5fc935ead 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/create_voice.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/create_voice.rs @@ -6,6 +6,7 @@ use std::fmt::Debug; use std::sync::Arc; use actix_web::{web, HttpRequest}; +use actix_web::web::Json; use log::{info, warn}; use serde::Deserialize; use serde::Serialize; @@ -55,9 +56,9 @@ const ROUTING_TAG_HEADER_NAME: &str = "routing-tag"; // pub async fn get_dataset_by_token pub async fn create_voice_handler( http_request: HttpRequest, - request: web::Json, + request: Json, server_state: web::Data> -) -> Result, CommonWebError> { +) -> Result, CommonWebError> { // voice data set token ... zsd_wb08asm0m4a4cz42wwqked6tfg6e e.g // need this for the job @@ -181,5 +182,5 @@ pub async fn create_voice_handler( inference_job_token: job_token, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/delete_voice.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/delete_voice.rs index 92f566062b..a0a4da58ee 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/delete_voice.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/delete_voice.rs @@ -2,6 +2,7 @@ use std::fmt; use std::sync::Arc; use actix_web::web::Path; +use actix_web::web::Json; use actix_web::{web, HttpRequest}; use log::warn; @@ -34,9 +35,9 @@ pub struct DeleteVoicePathInfo { pub async fn delete_voice_handler( http_request: HttpRequest, path: Path, - request: web::Json, + request: Json, server_state: web::Data> -) -> Result, CommonWebError>{ +) -> Result, CommonWebError>{ let maybe_user_session = server_state .session_checker .maybe_get_user_session(&http_request, &server_state.mysql_pool) @@ -136,5 +137,5 @@ pub async fn delete_voice_handler( } }; - Ok(web::Json(SimpleGenericJsonSuccess { success: true })) + Ok(Json(SimpleGenericJsonSuccess { success: true })) } \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/get_voice.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/get_voice.rs index d58150a742..dc94a6df6a 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/get_voice.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/get_voice.rs @@ -2,6 +2,7 @@ use std::fmt; use std::sync::Arc; use actix_web::web::Path; +use actix_web::web::Json; use actix_web::{web, HttpRequest}; use chrono::{DateTime, Utc}; use log::warn; @@ -40,7 +41,7 @@ pub async fn get_voice_handler( http_request: HttpRequest, path: Path, server_state: web::Data> -) -> Result, CommonWebError> { +) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -107,5 +108,5 @@ pub async fn get_voice_handler( updated_at: voice.updated_at, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/list_available_voices.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/list_available_voices.rs index 1eca058bc7..215fc78622 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/list_available_voices.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/list_available_voices.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use actix_web::{web, HttpRequest}; +use actix_web::web::Json; use chrono::{DateTime, Utc}; use log::{info, warn}; @@ -46,7 +47,7 @@ pub async fn list_available_voices_handler( http_request: HttpRequest, query: web::Query, server_state: web::Data> -) -> Result, CommonWebError> { +) -> Result, CommonWebError> { let maybe_user_session = server_state.session_checker.maybe_get_user_session( &http_request, @@ -156,7 +157,7 @@ pub async fn list_available_voices_handler( cursor_previous, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/list_voices_by_session.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/list_voices_by_session.rs index 70df37978e..fd095da662 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/list_voices_by_session.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/list_voices_by_session.rs @@ -2,6 +2,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::{web, HttpRequest}; use chrono::{DateTime, Utc}; @@ -60,7 +61,7 @@ impl ResponseError for ListVoicesByUserError { pub async fn list_voices_by_session_handler( http_request: HttpRequest, server_state: web::Data> -) -> Result, ListVoicesByUserError> { +) -> Result, ListVoicesByUserError> { let user_session = server_state .session_checker @@ -121,5 +122,5 @@ pub async fn list_voices_by_session_handler( voices, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/list_voices_by_user.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/list_voices_by_user.rs index ac8978f02c..7e5188b978 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/list_voices_by_user.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/list_voices_by_user.rs @@ -2,6 +2,7 @@ use std::fmt; use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::web::Path; use actix_web::{web, HttpRequest}; @@ -68,7 +69,7 @@ pub async fn list_voices_by_user_handler( http_request: HttpRequest, path: Path, server_state: web::Data> -) -> Result, ListVoicesByUserError> { +) -> Result, ListVoicesByUserError> { let maybe_user_session = server_state .session_checker @@ -134,7 +135,7 @@ pub async fn list_voices_by_user_handler( voices, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/update_voice.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/update_voice.rs index 6b36550f05..f695bda3c8 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/update_voice.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/update_voice.rs @@ -2,6 +2,7 @@ use std::fmt; use std::sync::Arc; use actix_web::web::Path; +use actix_web::web::Json; use actix_web::{web, HttpRequest}; use log::{error, warn}; @@ -44,8 +45,8 @@ pub struct UpdateVoicePathInfo { pub async fn update_voice_handler( http_request: HttpRequest, path: Path, - request: web::Json, - server_state: web::Data>) -> Result, CommonWebError> + request: Json, + server_state: web::Data>) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -163,5 +164,5 @@ pub async fn update_voice_handler( } }; - Ok(web::Json(SimpleGenericJsonSuccess { success: true })) + Ok(Json(SimpleGenericJsonSuccess { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/delete/delete_weight_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/delete/delete_weight_handler.rs index a0aa0c0188..b371a38e73 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/delete/delete_weight_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/delete/delete_weight_handler.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use actix_web::web::Path; +use actix_web::web::Json; use actix_web::{web, HttpRequest}; use log::warn; use utoipa::ToSchema; @@ -50,9 +51,9 @@ pub struct DeleteWeightRequest { pub async fn delete_weight_handler( http_request: HttpRequest, path: Path, - request: web::Json, + request: Json, server_state: web::Data> - ) -> Result, CommonWebError>{ + ) -> Result, CommonWebError>{ let maybe_user_session = server_state .session_checker .maybe_get_user_session(&http_request, &server_state.mysql_pool) @@ -150,5 +151,5 @@ pub async fn delete_weight_handler( } }; - Ok(web::Json(SimpleGenericJsonSuccess { success: true })) + Ok(Json(SimpleGenericJsonSuccess { success: true })) } \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/get/get_weight_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/get/get_weight_handler.rs index 59a755d18c..65e24c473c 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/get/get_weight_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/get/get_weight_handler.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use actix_web::web::Path; +use actix_web::web::Json; use actix_web::{web, HttpRequest}; use chrono::{DateTime, Utc}; use log::warn; @@ -102,7 +103,7 @@ pub async fn get_weight_handler( http_request: HttpRequest, path: Path, server_state: web::Data> -) -> Result, CommonWebError> { +) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker .maybe_get_user_session(&http_request, &server_state.mysql_pool) @@ -213,5 +214,5 @@ pub async fn get_weight_handler( updated_at: weight.updated_at, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_available_weights_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_available_weights_handler.rs index 5e255fc50b..da75a5ece9 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_available_weights_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_available_weights_handler.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::{web, HttpRequest}; use chrono::{DateTime, Utc}; @@ -142,7 +143,7 @@ pub async fn list_available_weights_handler( http_request: HttpRequest, query: web::Query, server_state: web::Data> -) -> Result, impl ResponseError> { +) -> Result, impl ResponseError> { let maybe_user_session = server_state.session_checker .maybe_get_user_session(&http_request, &server_state.mysql_pool).await .map_err(|e| { @@ -296,5 +297,5 @@ pub async fn list_available_weights_handler( }, }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_featured_weights_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_featured_weights_handler.rs index 988973690d..47b03afc0e 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_featured_weights_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_featured_weights_handler.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::web::Query; use actix_web::{web, HttpRequest}; @@ -140,7 +141,7 @@ pub async fn list_featured_weights_handler( http_request: HttpRequest, query: Query, server_state: web::Data> -) -> Result, impl ResponseError> { +) -> Result, impl ResponseError> { let maybe_user_session = server_state .session_checker @@ -280,5 +281,5 @@ pub async fn list_featured_weights_handler( }).collect::>(), }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_pinned_weights_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_pinned_weights_handler.rs index 8dcc9d0310..9348cd91d3 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_pinned_weights_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_pinned_weights_handler.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use actix_web::error::ResponseError; +use actix_web::web::Json; use actix_web::http::StatusCode; use actix_web::{web, HttpRequest}; use chrono::{DateTime, Utc}; @@ -108,7 +109,7 @@ impl ResponseError for ListPinnedWeightsError { pub async fn list_pinned_weights_handler( http_request: HttpRequest, server_state: web::Data> -) -> Result, impl ResponseError> { +) -> Result, impl ResponseError> { let mut redis = server_state.redis_pool.get() .map_err(|err| { @@ -201,5 +202,5 @@ pub async fn list_pinned_weights_handler( }).collect::>(), }; - Ok(web::Json(response)) + Ok(Json(response)) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_weights_by_user_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_weights_by_user_handler.rs index 6ef628657c..1335d7c9db 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_weights_by_user_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list/list_weights_by_user_handler.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use actix_web::web::{Path, Query}; +use actix_web::web::Json; use actix_web::{web, HttpRequest}; use chrono::{DateTime, Utc}; use log::warn; @@ -121,7 +122,7 @@ pub async fn list_weights_by_user_handler( path: Path, query: Query, server_state: web::Data> -) -> Result, CommonWebError> { +) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -245,5 +246,5 @@ pub async fn list_weights_by_user_handler( }; - Ok(web::Json(response)) + Ok(Json(response)) } \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/update/set_model_weight_cover_image_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/update/set_model_weight_cover_image_handler.rs index 7f51a764fc..e0a8e18d38 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/update/set_model_weight_cover_image_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/update/set_model_weight_cover_image_handler.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use actix_web::web::Path; +use actix_web::web::Json; use actix_web::{web, HttpRequest}; use log::warn; use utoipa::ToSchema; @@ -54,8 +55,8 @@ pub struct SetModelWeightCoverImagePathInfo { pub async fn set_model_weight_cover_image_handler( http_request: HttpRequest, path: Path, - request: web::Json, - server_state: web::Data>) -> Result, CommonWebError> + request: Json, + server_state: web::Data>) -> Result, CommonWebError> { let maybe_user_session = server_state .session_checker @@ -160,5 +161,5 @@ pub async fn set_model_weight_cover_image_handler( } }; - Ok(web::Json(SetModelWeightCoverImageResponse { success: true })) + Ok(Json(SetModelWeightCoverImageResponse { success: true })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/update/update_weight_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/update/update_weight_handler.rs index 57d21f99de..c016f2543a 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/update/update_weight_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/update/update_weight_handler.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use actix_web::web::Path; +use actix_web::web::Json; use actix_web::{web, HttpRequest}; use log::warn; use sqlx::MySqlPool; @@ -78,10 +79,10 @@ pub struct UpdateWeightPathInfo { pub async fn update_weight_handler( http_request: HttpRequest, path: Path, - request: web::Json, + request: Json, mysql_pool: web::Data, server_state: web::Data> -) -> Result, CommonWebError> { +) -> Result, CommonWebError> { let mut mysql_connection = mysql_pool .acquire() .await @@ -218,5 +219,5 @@ pub async fn update_weight_handler( } } - Ok(web::Json(SimpleGenericJsonSuccess { success: true })) + Ok(Json(SimpleGenericJsonSuccess { success: true })) } From ebe128718241c05acbd7156fbc99e30bdc491a6c Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Tue, 16 Jun 2026 01:34:50 -0400 Subject: [PATCH 10/53] misc cleanup, update claude docs (#1657) --- .claude/rules/api-conventions.md | 15 ++++++++++++--- .claude/rules/code-style.md | 9 +++++---- CLAUDE.md | 2 +- crates/service/web/storyteller_web/CLAUDE.md | 17 +++++++++++++++-- .../web_utils/response_error_helpers.rs | 2 +- .../web_utils/serialize_as_json_error.rs | 2 +- 6 files changed, 35 insertions(+), 12 deletions(-) diff --git a/.claude/rules/api-conventions.md b/.claude/rules/api-conventions.md index 8e489461e1..acd38325c5 100644 --- a/.claude/rules/api-conventions.md +++ b/.claude/rules/api-conventions.md @@ -19,9 +19,18 @@ Handlers in `storyteller_web` follow this structure: ### Session/Auth -- `require_user_session_using_connection()` reuses an open DB connection (preferred) -- `require_user_session()` acquires its own connection (deprecated) -- `require_moderator()` for admin-only endpoints +Helpers live in `storyteller_web` under `http_server/web_utils/user_session/`. Each takes a +generic `mysql_executor: E` (any `sqlx::Executor`), so whether it reuses a connection or acquires +its own depends on the **argument you pass**: + +- `require_user_session(http_request, session_checker, mysql_executor)` — require a logged-in user. +- `require_user_session_extended(...)` — same, with extra session detail. +- `require_moderator(http_request, session_checker, mysql_executor)` — admin-only endpoints. + +PREFER passing an already-open connection (`&mut *conn`) so the helper reuses it. Passing +`&server_state.mysql_pool` makes it acquire a fresh connection — avoid that when the handler +already holds one (the double-acquire adds pool pressure; this crate has had pool-timeout +incidents). There is no `*_using_connection` variant — the reuse/acquire choice is the argument. ### API Type Definitions diff --git a/.claude/rules/code-style.md b/.claude/rules/code-style.md index a6253b495c..e7a72079d5 100644 --- a/.claude/rules/code-style.md +++ b/.claude/rules/code-style.md @@ -7,10 +7,11 @@ - `#[macro_use] extern crate serde_derive;` is used in binary crates; library crates use `use serde_derive::{Serialize, Deserialize};` - Never use `println!` or `eprintln!` outside of tests; use `log` crate macros (`info!`, `warn!`, `error!`, `debug!`, `trace!`) instead - Use `anyhow::Result` / `AnyhowResult` for fallible functions in application code; new database queries should use `Result` instead -- SQLx queries: prefer `sqlx::query!` and `sqlx::query_as!` compile-time checked macros over - runtime `sqlx::query()` / `sqlx::query_as()`. The macros catch SQL typos, column mismatches, - and type errors at build time. Use runtime queries only when assembling complicated - dynamic/branching queries that cannot be expressed as a compile-time macro. +- SQLx: MySQL queries belong in the `mysql_queries` crate. Do NOT write `sqlx::query!` / + `sqlx::query()` in application/service crates (e.g. `storyteller-web`, which has zero direct + SQLx queries) — add or reuse a function in `mysql_queries` and call it. The compile-time + `query!` / `query_as!` macro conventions for that crate are documented in + `crates/schema/database/mysql_queries/CLAUDE.md`. - Enum Display/Debug: use `impl_enum_display_and_debug_using_to_str!` macro, not `derive_more::Display` (CLion doesn't understand it) - Fields that are optional use the `maybe_` prefix: `maybe_creator_user_token`, `maybe_prompt_token` - When implementing `fmt::Display` for error types, use the pattern: `write!(f, "{:?}", self)` diff --git a/CLAUDE.md b/CLAUDE.md index b2a7755987..3878d87808 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -58,7 +58,7 @@ artcraft/ - Rust with no minimum supported version - Actix-web for HTTP services -- SQLx for MySQL and SQLite; prefer `sqlx::query!` / `sqlx::query_as!` compile-time checked macros over runtime `sqlx::query()` whenever possible +- SQLx for MySQL and SQLite. MySQL queries belong in the `mysql_queries` crate, not in application/service crates like `storyteller-web` — call those query functions instead of embedding `sqlx::query!` / `sqlx::query()` in handlers. (Query-writing conventions live in `crates/schema/database/mysql_queries/CLAUDE.md`.) - A mix of wreq and reqwest for Rust HTTP clients - Never use `println!` or `eprintln!` outside of tests; use `log` crate macros instead - When two crates export the same type name, alias imports with a suffix: `use foo::Bar as BarFoo;` diff --git a/crates/service/web/storyteller_web/CLAUDE.md b/crates/service/web/storyteller_web/CLAUDE.md index 9fc47c4e87..93bfbb00d5 100644 --- a/crates/service/web/storyteller_web/CLAUDE.md +++ b/crates/service/web/storyteller_web/CLAUDE.md @@ -28,8 +28,21 @@ deprecated or only used when setting other HTTP headers, like cookies. Prefer `CommonWebError` for new handlers unless you need custom error variants. -When a handler uses `require_user_session`, prefer `require_user_session_using_connection` -which reuses an existing DB connection. +The session helpers (`require_user_session`, `require_user_session_extended`, `require_moderator` +in `http_server/web_utils/user_session/`) take a generic `mysql_executor` argument. Pass an +already-open connection (`&mut *conn`) so the helper reuses it; do NOT pass +`&server_state.mysql_pool` when the handler already holds a connection, since that self-acquires a +second one and adds pool pressure (this crate has had pool-timeout incidents). There is no +`require_user_session_using_connection` / `require_moderator_using_connection` — the +reuse-vs-acquire behavior is determined by the argument. + +## Database Access + +Do NOT write SQLx queries in this crate. `storyteller-web` contains **zero** direct SQLx queries — +all MySQL access goes through the `mysql_queries` crate. Add a function there (or reuse an existing +one) and call it from the handler; never embed `sqlx::query!` / `sqlx::query()` in a handler or +helper here. See `crates/schema/database/mysql_queries/CLAUDE.md` for the query-writing conventions +(compile-time `query!` / `query_as!` macros, offline cache, the `Args`/`Executor` pattern, etc.). ## api_doc.rs diff --git a/crates/service/web/storyteller_web/src/http_server/web_utils/response_error_helpers.rs b/crates/service/web/storyteller_web/src/http_server/web_utils/response_error_helpers.rs index 41ffdca006..73cef455c1 100644 --- a/crates/service/web/storyteller_web/src/http_server/web_utils/response_error_helpers.rs +++ b/crates/service/web/storyteller_web/src/http_server/web_utils/response_error_helpers.rs @@ -25,6 +25,6 @@ pub fn to_simple_json_error( }; HttpResponseBuilder::new(status_code) - .set_header(CONTENT_TYPE, "application/json") + .insert_header((CONTENT_TYPE, "application/json")) .body(body) } \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/http_server/web_utils/serialize_as_json_error.rs b/crates/service/web/storyteller_web/src/http_server/web_utils/serialize_as_json_error.rs index b4aa6fa1ab..8ca8857a03 100644 --- a/crates/service/web/storyteller_web/src/http_server/web_utils/serialize_as_json_error.rs +++ b/crates/service/web/storyteller_web/src/http_server/web_utils/serialize_as_json_error.rs @@ -16,6 +16,6 @@ pub fn serialize_as_json_error( }; HttpResponseBuilder::new(error_payload.status_code()) - .set_header(CONTENT_TYPE, "application/json") + .insert_header((CONTENT_TYPE, "application/json")) .body(body) } From a0b6047a78c158aa3133ff60fdfa991beb8513da Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Tue, 16 Jun 2026 01:42:01 -0400 Subject: [PATCH 11/53] refactor module location --- .../workflows/enqueue/enqueue_face_fusion_workflow_handler.rs | 2 +- .../workflows/enqueue/enqueue_live_portrait_workflow_handler.rs | 2 +- .../http_server/endpoints/api_keys/create_api_key_handler.rs | 2 +- .../http_server/endpoints/api_keys/delete_api_key_handler.rs | 2 +- .../src/http_server/endpoints/api_keys/get_api_key_handler.rs | 2 +- .../src/http_server/endpoints/api_keys/list_api_keys_handler.rs | 2 +- .../http_server/endpoints/api_keys/update_api_key_handler.rs | 2 +- .../http_server/endpoints/beta_keys/create_beta_keys_handler.rs | 2 +- .../beta_keys/edit_beta_key_distributed_flag_handler.rs | 2 +- .../endpoints/beta_keys/edit_beta_key_note_handler.rs | 2 +- .../http_server/endpoints/beta_keys/list_beta_keys_handler.rs | 2 +- .../http_server/endpoints/beta_keys/redeem_beta_key_handler.rs | 2 +- .../endpoints/characters/delete_character_handler.rs | 2 +- .../http_server/endpoints/characters/edit_character_handler.rs | 2 +- .../http_server/endpoints/characters/list_characters_handler.rs | 2 +- .../endpoints/folders/folder/color_code_folder_handler.rs | 2 +- .../endpoints/folders/folder/cover_image_folder_handler.rs | 2 +- .../endpoints/folders/folder/create_folder_handler.rs | 2 +- .../endpoints/folders/folder/delete_folder_handler.rs | 2 +- .../http_server/endpoints/folders/folder/get_folder_handler.rs | 2 +- .../endpoints/folders/folder/list_folders_handler.rs | 2 +- .../endpoints/folders/folder/rename_folder_handler.rs | 2 +- .../http_server/endpoints/folders/folder/star_folder_handler.rs | 2 +- .../folders/media_files/bulk_add_folder_media_files_handler.rs | 2 +- .../folders/media_files/bulk_move_folder_media_files_handler.rs | 2 +- .../media_files/bulk_remove_folder_media_files_handler.rs | 2 +- .../folders/media_files/list_folder_media_files_handler.rs | 2 +- .../endpoints/folders/subfolder/bulk_add_subfolders_handler.rs | 2 +- .../folders/subfolder/bulk_remove_subfolders_handler.rs | 2 +- .../endpoints/folders/subfolder/list_subfolders_handler.rs | 2 +- .../delete/dismiss_finished_session_jobs_handler.rs | 2 +- .../edit/change_media_file_animation_type_handler.rs | 2 +- .../edit/change_media_file_engine_category_handler.rs | 2 +- .../upload/upload_pmx/upload_pmx_media_file_handler.rs | 2 +- .../upload_studio_shot/upload_studio_shot_media_file_handler.rs | 2 +- .../model_download/enqueue_gptsovits_model_download_handler.rs | 2 +- .../moderation/alerts/moderation_send_alert_handler.rs | 2 +- .../debug_logs/moderation_list_debug_logs_for_token_handler.rs | 2 +- .../endpoints/moderation/info/moderator_token_info_handler.rs | 2 +- .../moderation/jobs/moderation_get_job_by_token_handler.rs | 2 +- .../endpoints/moderation/jobs/user/list_user_jobs_handler.rs | 2 +- .../staff_audit_logs/moderator_list_staff_audit_logs_handler.rs | 2 +- .../user/moderator_list_subscribing_users_by_signup_date.rs | 2 +- .../moderation/user/moderator_list_users_by_signup_date.rs | 2 +- .../user/moderator_user_lookup_by_stripe_customer_id_handler.rs | 2 +- .../endpoints/moderation/user/moderator_user_lookup_handler.rs | 2 +- .../moderation/user_bans/moderation_ban_user_handler.rs | 2 +- .../user_emails/moderator_change_user_email_handler.rs | 2 +- .../moderator_list_email_address_changes_for_user_handler.rs | 2 +- .../moderator_edit_user_feature_flags_handler.rs | 2 +- .../moderator_list_all_available_user_feature_flags_handler.rs | 2 +- .../moderator_list_user_feature_flags_handler.rs | 2 +- .../moderator_list_global_user_referrals_handler.rs | 2 +- .../moderator_list_user_referrals_for_user_handler.rs | 2 +- ...list_user_session_impersonation_requests_for_user_handler.rs | 2 +- ...oderator_list_user_session_impersonation_requests_handler.rs | 2 +- .../moderator_user_session_impersonation_request_handler.rs | 2 +- .../moderator_get_user_stripe_customer_ids_handler.rs | 2 +- .../list_wallet_ledger_entries_by_wallet_handler.rs | 2 +- .../moderator_get_wallet_ledger_entry_handler.rs | 2 +- .../endpoints/moderation/wallets/list_user_wallets_handler.rs | 2 +- .../wallets/moderator_add_banked_balance_to_wallet_handler.rs | 2 +- .../wallets/moderator_create_wallet_for_user_handler.rs | 2 +- .../moderation/wallets/moderator_get_wallet_handler.rs | 2 +- .../http_server/endpoints/tags/set_tags_for_entity_handler.rs | 2 +- .../src/http_server/endpoints/users/change_password_handler.rs | 2 +- .../src/http_server/endpoints/users/edit_email_handler.rs | 2 +- .../src/http_server/endpoints/users/edit_username_handler.rs | 2 +- .../endpoints/weights/update/update_weight_handler.rs | 2 +- .../web/storyteller_web/src/http_server/user_lookup/mod.rs | 1 + .../http_server/{web_utils => user_lookup}/user_session/mod.rs | 0 .../user_session/require_moderator.rs | 0 .../user_session/require_user_session.rs | 0 .../user_session/require_user_session_extended.rs | 0 .../web/storyteller_web/src/http_server/web_utils/mod.rs | 1 - 75 files changed, 70 insertions(+), 70 deletions(-) rename crates/service/web/storyteller_web/src/http_server/{web_utils => user_lookup}/user_session/mod.rs (100%) rename crates/service/web/storyteller_web/src/http_server/{web_utils => user_lookup}/user_session/require_moderator.rs (100%) rename crates/service/web/storyteller_web/src/http_server/{web_utils => user_lookup}/user_session/require_user_session.rs (100%) rename crates/service/web/storyteller_web/src/http_server/{web_utils => user_lookup}/user_session/require_user_session_extended.rs (100%) diff --git a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue/enqueue_face_fusion_workflow_handler.rs b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue/enqueue_face_fusion_workflow_handler.rs index e069a6fb38..32f7bdf83d 100644 --- a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue/enqueue_face_fusion_workflow_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue/enqueue_face_fusion_workflow_handler.rs @@ -28,7 +28,7 @@ use crate::http_server::requests::request_headers::get_routing_tag_header::get_r use crate::http_server::requests::request_headers::has_debug_header::has_debug_header; use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; -use crate::http_server::web_utils::user_session::require_user_session_extended::require_user_session_extended; +use crate::http_server::user_lookup::user_session::require_user_session_extended::require_user_session_extended; use crate::state::server_state::ServerState; use crate::util::cleaners::empty_media_file_token_to_null::empty_media_file_token_to_null; diff --git a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue/enqueue_live_portrait_workflow_handler.rs b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue/enqueue_live_portrait_workflow_handler.rs index 18b58ba0fc..0a56d3711e 100644 --- a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue/enqueue_live_portrait_workflow_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue/enqueue_live_portrait_workflow_handler.rs @@ -29,7 +29,7 @@ use crate::http_server::requests::request_headers::get_routing_tag_header::get_r use crate::http_server::requests::request_headers::has_debug_header::has_debug_header; use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; -use crate::http_server::web_utils::user_session::require_user_session_extended::require_user_session_extended; +use crate::http_server::user_lookup::user_session::require_user_session_extended::require_user_session_extended; use crate::state::server_state::ServerState; use crate::util::cleaners::empty_media_file_token_to_null::empty_media_file_token_to_null; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/create_api_key_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/create_api_key_handler.rs index f08b8d7190..5500f3e515 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/create_api_key_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/create_api_key_handler.rs @@ -11,7 +11,7 @@ use mysql_queries::queries::api_keys::insert_api_key::{insert_api_key, InsertApi use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; -use crate::http_server::web_utils::user_session::require_user_session::require_user_session; +use crate::http_server::user_lookup::user_session::require_user_session::require_user_session; use crate::state::server_state::ServerState; use crate::util::generate_api_key::generate_api_key; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/delete_api_key_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/delete_api_key_handler.rs index 8d00af94ef..062048ee80 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/delete_api_key_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/delete_api_key_handler.rs @@ -14,7 +14,7 @@ use mysql_queries::queries::api_keys::get_api_key_by_token::{ use tokens::tokens::api_keys::ApiKeyToken; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_user_session::require_user_session; +use crate::http_server::user_lookup::user_session::require_user_session::require_user_session; use crate::state::server_state::ServerState; /// Soft-delete an API key by `token`, scoped to the logged-in user. diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/get_api_key_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/get_api_key_handler.rs index 7f08ebd985..3f22317a08 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/get_api_key_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/get_api_key_handler.rs @@ -14,7 +14,7 @@ use tokens::tokens::api_keys::ApiKeyToken; use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::http_server::endpoints::api_keys::api_key_info_conversion::api_key_row_to_info; -use crate::http_server::web_utils::user_session::require_user_session::require_user_session; +use crate::http_server::user_lookup::user_session::require_user_session::require_user_session; use crate::state::server_state::ServerState; /// Fetch a single API key by its `token`, scoped to the logged-in user. diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/list_api_keys_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/list_api_keys_handler.rs index d3d9d41401..52b58d5110 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/list_api_keys_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/list_api_keys_handler.rs @@ -12,7 +12,7 @@ use mysql_queries::queries::api_keys::list_api_keys_for_user::{ use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::http_server::endpoints::api_keys::api_key_info_conversion::api_key_row_to_info; -use crate::http_server::web_utils::user_session::require_user_session::require_user_session; +use crate::http_server::user_lookup::user_session::require_user_session::require_user_session; use crate::state::server_state::ServerState; const DEFAULT_LIMIT: u32 = 100; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/update_api_key_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/update_api_key_handler.rs index 30c2ff7c67..f8a035d2da 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/update_api_key_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/update_api_key_handler.rs @@ -15,7 +15,7 @@ use mysql_queries::queries::api_keys::update_api_key::{update_api_key, UpdateApi use tokens::tokens::api_keys::ApiKeyToken; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_user_session::require_user_session; +use crate::http_server::user_lookup::user_session::require_user_session::require_user_session; use crate::state::server_state::ServerState; const MAX_DESCRIPTION_LEN: usize = 512; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/create_beta_keys_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/create_beta_keys_handler.rs index b9a384188a..9d4bfaebfb 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/create_beta_keys_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/create_beta_keys_handler.rs @@ -15,7 +15,7 @@ use mysql_queries::queries::users::user_profiles::get_user_profile_by_username:: use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_moderator::require_moderator; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; const MAXIMUM_KEYS : u32 = 100; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/edit_beta_key_distributed_flag_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/edit_beta_key_distributed_flag_handler.rs index 6915e6be8b..d72ecf4a95 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/edit_beta_key_distributed_flag_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/edit_beta_key_distributed_flag_handler.rs @@ -15,7 +15,7 @@ use tokens::tokens::w2l_results::W2lResultToken; use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_moderator::require_moderator; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; /// For the URL PathInfo diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/edit_beta_key_note_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/edit_beta_key_note_handler.rs index c6faf95677..44d107479d 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/edit_beta_key_note_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/edit_beta_key_note_handler.rs @@ -14,7 +14,7 @@ use tokens::tokens::beta_keys::BetaKeyToken; use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_moderator::require_moderator; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; /// For the URL PathInfo diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/list_beta_keys_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/list_beta_keys_handler.rs index f25af9b861..419696610b 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/list_beta_keys_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/list_beta_keys_handler.rs @@ -18,7 +18,7 @@ use tokens::tokens::beta_keys::BetaKeyToken; use crate::http_server::common_responses::pagination_page::PaginationPage; use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::http_server::common_responses::user_details_lite::UserDetailsLight; -use crate::http_server::web_utils::user_session::require_user_session::require_user_session; +use crate::http_server::user_lookup::user_session::require_user_session::require_user_session; use crate::state::server_state::ServerState; #[derive(Copy, Clone, Deserialize, ToSchema)] diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/redeem_beta_key_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/redeem_beta_key_handler.rs index d8f3ce0438..ef87ab27cf 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/redeem_beta_key_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/redeem_beta_key_handler.rs @@ -17,7 +17,7 @@ use mysql_queries::queries::users::user::update::set_user_feature_flags_transact use mysql_queries::queries::users::user_sessions::get_user_session_by_token::SessionUserRecord; use crate::http_server::web_utils::try_delete_session_cache::try_delete_session_cache; -use crate::http_server::web_utils::user_session::require_user_session::require_user_session; +use crate::http_server::user_lookup::user_session::require_user_session::require_user_session; use crate::state::server_state::ServerState; #[derive(Deserialize, ToSchema)] diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/characters/delete_character_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/characters/delete_character_handler.rs index 5f83e61416..28ae40e930 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/characters/delete_character_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/characters/delete_character_handler.rs @@ -10,7 +10,7 @@ use mysql_queries::queries::characters::delete_character::delete_character; use mysql_queries::queries::characters::get_character_by_token_including_deleted::get_character_by_token_including_deleted; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_user_session::require_user_session; +use crate::http_server::user_lookup::user_session::require_user_session::require_user_session; use crate::state::server_state::ServerState; /// Delete a character. diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/characters/edit_character_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/characters/edit_character_handler.rs index 3160c7967d..5cbfb8dca3 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/characters/edit_character_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/characters/edit_character_handler.rs @@ -11,7 +11,7 @@ use seedance2pro_client::creds::seedance2pro_session::Seedance2ProSession; use seedance2pro_client::requests::update_character::update_character::{update_character, UpdateCharacterArgs}; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_user_session::require_user_session; +use crate::http_server::user_lookup::user_session::require_user_session::require_user_session; use crate::state::server_state::ServerState; /// Edit a character's name or description. diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/characters/list_characters_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/characters/list_characters_handler.rs index 89c21f12e9..d65b245f47 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/characters/list_characters_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/characters/list_characters_handler.rs @@ -16,7 +16,7 @@ use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::http_server::common_responses::media::media_domain::MediaDomain; use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder; use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain; -use crate::http_server::web_utils::user_session::require_user_session::require_user_session; +use crate::http_server::user_lookup::user_session::require_user_session::require_user_session; use crate::state::server_state::ServerState; /// List characters for the current session. diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/color_code_folder_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/color_code_folder_handler.rs index 74082d579b..d2cdd9422f 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/color_code_folder_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/color_code_folder_handler.rs @@ -17,7 +17,7 @@ use mysql_queries::queries::folders::folder::update_folder_color_code::{ use tokens::tokens::folders::FolderToken; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_user_session::require_user_session; +use crate::http_server::user_lookup::user_session::require_user_session::require_user_session; use crate::state::server_state::ServerState; const MAX_COLOR_CODE_LEN: usize = 16; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/cover_image_folder_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/cover_image_folder_handler.rs index d08c5d9d3a..48841acd6e 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/cover_image_folder_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/cover_image_folder_handler.rs @@ -23,7 +23,7 @@ use tokens::tokens::folders::FolderToken; use tokens::tokens::media_files::MediaFileToken; use tokens::tokens::users::UserToken; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_user_session::require_user_session; +use crate::http_server::user_lookup::user_session::require_user_session::require_user_session; use crate::state::server_state::ServerState; /// Set or clear the custom cover image on a folder. diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/create_folder_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/create_folder_handler.rs index c0f21cfac0..903fb286dc 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/create_folder_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/create_folder_handler.rs @@ -17,7 +17,7 @@ use crate::http_server::endpoints::folders::folder::folder_info_conversion::{ build_folder_thumbnails_lookup, folder_row_to_info, }; use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain; -use crate::http_server::web_utils::user_session::require_user_session::require_user_session; +use crate::http_server::user_lookup::user_session::require_user_session::require_user_session; use crate::state::server_state::ServerState; const MAX_NAME_LEN: usize = 255; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/delete_folder_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/delete_folder_handler.rs index dcc099d654..ae25633a58 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/delete_folder_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/delete_folder_handler.rs @@ -12,7 +12,7 @@ use mysql_queries::queries::folders::folder::soft_delete_folder::{ use tokens::tokens::folders::FolderToken; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_user_session::require_user_session; +use crate::http_server::user_lookup::user_session::require_user_session::require_user_session; use crate::state::server_state::ServerState; /// Soft-delete a folder. Children retain their parent pointer and become diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/get_folder_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/get_folder_handler.rs index e065f67b2c..bcca1a4238 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/get_folder_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/get_folder_handler.rs @@ -16,7 +16,7 @@ use crate::http_server::endpoints::folders::folder::folder_info_conversion::{ build_folder_thumbnails_lookup, folder_row_to_info, }; use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain; -use crate::http_server::web_utils::user_session::require_user_session::require_user_session; +use crate::http_server::user_lookup::user_session::require_user_session::require_user_session; use crate::state::server_state::ServerState; /// Fetch a single folder owned by the logged-in user. diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/list_folders_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/list_folders_handler.rs index 4016e06d5d..eb9bfed464 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/list_folders_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/list_folders_handler.rs @@ -17,7 +17,7 @@ use crate::http_server::endpoints::folders::folder::folder_info_conversion::{ build_folder_thumbnails_lookup, folder_row_to_info, }; use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain; -use crate::http_server::web_utils::user_session::require_user_session::require_user_session; +use crate::http_server::user_lookup::user_session::require_user_session::require_user_session; use crate::state::server_state::ServerState; const CURSOR_NAME: &str = "folders_all"; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/rename_folder_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/rename_folder_handler.rs index 97a785637c..132b3088b5 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/rename_folder_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/rename_folder_handler.rs @@ -17,7 +17,7 @@ use mysql_queries::queries::folders::folder::update_folder_name::{ use tokens::tokens::folders::FolderToken; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_user_session::require_user_session; +use crate::http_server::user_lookup::user_session::require_user_session::require_user_session; use crate::state::server_state::ServerState; const MAX_NAME_LEN: usize = 255; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/star_folder_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/star_folder_handler.rs index a393890e5a..232b490326 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/star_folder_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/folder/star_folder_handler.rs @@ -17,7 +17,7 @@ use mysql_queries::queries::folders::folder::update_folder_has_star::{ use tokens::tokens::folders::FolderToken; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_user_session::require_user_session; +use crate::http_server::user_lookup::user_session::require_user_session::require_user_session; use crate::state::server_state::ServerState; #[utoipa::path( diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/media_files/bulk_add_folder_media_files_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/media_files/bulk_add_folder_media_files_handler.rs index 0bd375f7f7..8d3c732b9a 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/media_files/bulk_add_folder_media_files_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/media_files/bulk_add_folder_media_files_handler.rs @@ -27,7 +27,7 @@ use mysql_queries::queries::folders::media_files::recompute_folder_last_media_fi use tokens::tokens::folders::FolderToken; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_user_session::require_user_session; +use crate::http_server::user_lookup::user_session::require_user_session::require_user_session; use crate::state::server_state::ServerState; const MAX_BULK: usize = 500; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/media_files/bulk_move_folder_media_files_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/media_files/bulk_move_folder_media_files_handler.rs index d14ee13773..57be27b8dd 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/media_files/bulk_move_folder_media_files_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/media_files/bulk_move_folder_media_files_handler.rs @@ -30,7 +30,7 @@ use mysql_queries::queries::folders::media_files::recompute_folder_last_media_fi use tokens::tokens::folders::FolderToken; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_user_session::require_user_session; +use crate::http_server::user_lookup::user_session::require_user_session::require_user_session; use crate::state::server_state::ServerState; const MAX_BULK: usize = 500; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/media_files/bulk_remove_folder_media_files_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/media_files/bulk_remove_folder_media_files_handler.rs index d002b0d050..be67b82dae 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/media_files/bulk_remove_folder_media_files_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/media_files/bulk_remove_folder_media_files_handler.rs @@ -24,7 +24,7 @@ use tokens::tokens::folders::FolderToken; use tokens::tokens::media_files::MediaFileToken; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_user_session::require_user_session; +use crate::http_server::user_lookup::user_session::require_user_session::require_user_session; use crate::state::server_state::ServerState; const MAX_BULK: usize = 500; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/media_files/list_folder_media_files_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/media_files/list_folder_media_files_handler.rs index 568cb69d62..549ee2de7d 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/media_files/list_folder_media_files_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/media_files/list_folder_media_files_handler.rs @@ -32,7 +32,7 @@ use crate::http_server::common_responses::media::media_domain::MediaDomain; use crate::http_server::common_responses::media::media_file_cover_image_details::MediaFileCoverImageDetails; use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder; use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain; -use crate::http_server::web_utils::user_session::require_user_session::require_user_session; +use crate::http_server::user_lookup::user_session::require_user_session::require_user_session; use crate::state::server_state::ServerState; const CURSOR_NAME: &str = "folder_mf"; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/subfolder/bulk_add_subfolders_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/subfolder/bulk_add_subfolders_handler.rs index c9f6ec1051..0d5ff0f033 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/subfolder/bulk_add_subfolders_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/subfolder/bulk_add_subfolders_handler.rs @@ -25,7 +25,7 @@ use mysql_queries::queries::folders::subfolder::filter_existing_owned_folder_tok use tokens::tokens::folders::FolderToken; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_user_session::require_user_session; +use crate::http_server::user_lookup::user_session::require_user_session::require_user_session; use crate::state::server_state::ServerState; const MAX_BULK: usize = 500; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/subfolder/bulk_remove_subfolders_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/subfolder/bulk_remove_subfolders_handler.rs index 88eb6aecc6..8a0eab5640 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/subfolder/bulk_remove_subfolders_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/subfolder/bulk_remove_subfolders_handler.rs @@ -14,7 +14,7 @@ use mysql_queries::queries::folders::subfolder::bulk_clear_parent_folder::{ use tokens::tokens::folders::FolderToken; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_user_session::require_user_session; +use crate::http_server::user_lookup::user_session::require_user_session::require_user_session; use crate::state::server_state::ServerState; const MAX_BULK: usize = 500; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/subfolder/list_subfolders_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/subfolder/list_subfolders_handler.rs index e3fae72cbf..50f1a73e3b 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/subfolder/list_subfolders_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/subfolder/list_subfolders_handler.rs @@ -21,7 +21,7 @@ use crate::http_server::endpoints::folders::folder::folder_info_conversion::{ build_folder_thumbnails_lookup, folder_row_to_info, }; use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain; -use crate::http_server::web_utils::user_session::require_user_session::require_user_session; +use crate::http_server::user_lookup::user_session::require_user_session::require_user_session; use crate::state::server_state::ServerState; const CURSOR_NAME: &str = "subfolders"; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/inference_job/delete/dismiss_finished_session_jobs_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/inference_job/delete/dismiss_finished_session_jobs_handler.rs index 5a1a055194..864d655b2e 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/inference_job/delete/dismiss_finished_session_jobs_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/inference_job/delete/dismiss_finished_session_jobs_handler.rs @@ -8,7 +8,7 @@ use utoipa::ToSchema; use mysql_queries::queries::generic_inference::web::dismiss_finished_jobs_for_user::dismiss_finished_jobs_for_user; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_user_session::require_user_session; +use crate::http_server::user_lookup::user_session::require_user_session::require_user_session; use crate::state::server_state::ServerState; #[derive(Serialize, ToSchema)] diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/change_media_file_animation_type_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/change_media_file_animation_type_handler.rs index ffc66e7e5e..8c528829a9 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/change_media_file_animation_type_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/change_media_file_animation_type_handler.rs @@ -17,7 +17,7 @@ use mysql_queries::queries::media_files::get::get_media_file::get_media_file; use crate::http_server::common_requests::media_file_token_path_info::MediaFileTokenPathInfo; use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::http_server::common_responses::simple_response::SimpleResponse; -use crate::http_server::web_utils::user_session::require_user_session::require_user_session; +use crate::http_server::user_lookup::user_session::require_user_session::require_user_session; use crate::state::server_state::ServerState; #[derive(Deserialize, ToSchema)] diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/change_media_file_engine_category_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/change_media_file_engine_category_handler.rs index 6ecda0dfc5..23f0863a30 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/change_media_file_engine_category_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/edit/change_media_file_engine_category_handler.rs @@ -16,7 +16,7 @@ use mysql_queries::queries::media_files::get::get_media_file::get_media_file; use crate::http_server::common_requests::media_file_token_path_info::MediaFileTokenPathInfo; use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::http_server::common_responses::simple_response::SimpleResponse; -use crate::http_server::web_utils::user_session::require_user_session::require_user_session; +use crate::http_server::user_lookup::user_session::require_user_session::require_user_session; use crate::state::server_state::ServerState; #[derive(Deserialize, ToSchema)] diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_pmx/upload_pmx_media_file_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_pmx/upload_pmx_media_file_handler.rs index a83faabc9a..652182e390 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_pmx/upload_pmx_media_file_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_pmx/upload_pmx_media_file_handler.rs @@ -24,7 +24,7 @@ use crate::http_server::endpoints::media_files::upload::upload_error::MediaFileU use crate::http_server::endpoints::media_files::upload::upload_pmx::extract_and_upload_pmx_files::{extract_and_upload_pmx_files, PmxError}; use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_moderator::require_moderator; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; /// Form-multipart request fields. diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_studio_shot/upload_studio_shot_media_file_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_studio_shot/upload_studio_shot_media_file_handler.rs index 4ec61b108b..654c0313e8 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_studio_shot/upload_studio_shot_media_file_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_studio_shot/upload_studio_shot_media_file_handler.rs @@ -25,7 +25,7 @@ use crate::http_server::endpoints::media_files::upload::upload_studio_shot::extr use crate::http_server::endpoints::media_files::upload::upload_studio_shot::ffmpeg_frames_to_mp4::ffmpeg_frames_to_mp4; use crate::http_server::requests::request_headers::has_debug_header::has_debug_header; use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; -use crate::http_server::web_utils::user_session::require_user_session_extended::require_user_session_extended; +use crate::http_server::user_lookup::user_session::require_user_session_extended::require_user_session_extended; use crate::state::server_state::ServerState; const DEFAULT_FPS : u8 = 12; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/model_download/enqueue_gptsovits_model_download_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/model_download/enqueue_gptsovits_model_download_handler.rs index 0e6a8c85df..9db8b015c3 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/model_download/enqueue_gptsovits_model_download_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/model_download/enqueue_gptsovits_model_download_handler.rs @@ -28,7 +28,7 @@ use crate::http_server::requests::request_headers::get_routing_tag_header::get_r use crate::http_server::requests::request_headers::has_debug_header::has_debug_header; use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; use crate::http_server::validations::validate_model_title::validate_model_title; -use crate::http_server::web_utils::user_session::require_user_session_extended::require_user_session_extended; +use crate::http_server::user_lookup::user_session::require_user_session_extended::require_user_session_extended; use crate::state::server_state::ServerState; #[derive(Deserialize, ToSchema)] diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/alerts/moderation_send_alert_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/alerts/moderation_send_alert_handler.rs index c7f8cc01e4..b11515ddb8 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/alerts/moderation_send_alert_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/alerts/moderation_send_alert_handler.rs @@ -14,7 +14,7 @@ use mysql_queries::queries::staff_audit_logs::insert_staff_audit_log::{ use pager::notification::notification_details_builder::NotificationDetailsBuilder; use pager::notification::notification_urgency::NotificationUrgency; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_moderator::require_moderator; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; /// Send a test alert via the pager system. Moderators only. 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 index 04e854b8b6..eb2402049f 100644 --- 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 @@ -15,7 +15,7 @@ use tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken; use tokens::tokens::users::UserToken; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_moderator::require_moderator; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; // ── Path params ── diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/info/moderator_token_info_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/info/moderator_token_info_handler.rs index 8479d0cbf3..598a81afbf 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/info/moderator_token_info_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/info/moderator_token_info_handler.rs @@ -26,7 +26,7 @@ use tokens::tokens::users::UserToken; use crate::http_server::web_utils::serialize_as_json_error::serialize_as_json_error; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_moderator::require_moderator; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; /// For the URL PathInfo 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 index 40813847ee..ca9d4e533f 100644 --- 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 @@ -16,7 +16,7 @@ use tokens::tokens::prompts::PromptToken; use tokens::tokens::users::UserToken; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_moderator::require_moderator; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; // ── Path params ── diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/user/list_user_jobs_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/user/list_user_jobs_handler.rs index 56256256c8..cb3c16c86b 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/user/list_user_jobs_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/user/list_user_jobs_handler.rs @@ -13,7 +13,7 @@ use mysql_queries::queries::generic_inference::web::list_user_jobs_for_moderatio use tokens::tokens::users::UserToken; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_moderator::require_moderator; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; /// List jobs for a user (moderation) diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/staff_audit_logs/moderator_list_staff_audit_logs_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/staff_audit_logs/moderator_list_staff_audit_logs_handler.rs index 98719e5e80..764f948e0e 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/staff_audit_logs/moderator_list_staff_audit_logs_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/staff_audit_logs/moderator_list_staff_audit_logs_handler.rs @@ -13,7 +13,7 @@ use tokens::tokens::staff_audit_logs::StaffAuditLogToken; use tokens::tokens::users::UserToken; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_moderator::require_moderator; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; const CURSOR_NAME: &str = "modstfaud"; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user/moderator_list_subscribing_users_by_signup_date.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user/moderator_list_subscribing_users_by_signup_date.rs index d51dc46dc6..afde1db281 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user/moderator_list_subscribing_users_by_signup_date.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user/moderator_list_subscribing_users_by_signup_date.rs @@ -15,7 +15,7 @@ use mysql_queries::queries::users::user::list::list_subscribing_users_by_signup_ }; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_moderator::require_moderator; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; /// List subscribing users by signup date (descending), for moderators. diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user/moderator_list_users_by_signup_date.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user/moderator_list_users_by_signup_date.rs index 711be69d67..9016480907 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user/moderator_list_users_by_signup_date.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user/moderator_list_users_by_signup_date.rs @@ -15,7 +15,7 @@ use mysql_queries::queries::users::user::list::list_users_by_signup_date_for_mod }; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_moderator::require_moderator; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; /// List users by signup date (descending), for moderators. 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 index 3ffb961167..46b7f76dc3 100644 --- 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 @@ -12,7 +12,7 @@ use artcraft_api_defs::moderation::user::user_lookup_by_stripe_customer_id::{ 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; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; /// Moderator User Lookup by Stripe Customer ID 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 feb99cbe5e..a168539c4c 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 @@ -17,7 +17,7 @@ use mysql_queries::queries::users::user::get::lookup_user_for_moderation::{ use tokens::tokens::users::UserToken; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_moderator::require_moderator; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; const LEGACY_USER_TOKEN_PREFIX: &str = "U:"; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_bans/moderation_ban_user_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_bans/moderation_ban_user_handler.rs index 5949ecd571..d8399d89c7 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_bans/moderation_ban_user_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_bans/moderation_ban_user_handler.rs @@ -18,7 +18,7 @@ use mysql_queries::queries::users::user::update::set_user_ban_status::{ use mysql_queries::queries::users::user_profiles::get_user_profile_by_username::get_user_profile_by_username; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_moderator::require_moderator; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; // --- Request --- diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_emails/moderator_change_user_email_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_emails/moderator_change_user_email_handler.rs index 77dedaa0a0..73489d7689 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_emails/moderator_change_user_email_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_emails/moderator_change_user_email_handler.rs @@ -24,7 +24,7 @@ use users::email::email_to_gravatar_hash::email_to_gravatar_hash; use users::email::validate_email_address_format::validate_email_address_format; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_moderator::require_moderator; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; // --- Request --- diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_emails/moderator_list_email_address_changes_for_user_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_emails/moderator_list_email_address_changes_for_user_handler.rs index ccb4b84a9e..b47fce3bac 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_emails/moderator_list_email_address_changes_for_user_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_emails/moderator_list_email_address_changes_for_user_handler.rs @@ -15,7 +15,7 @@ use mysql_queries::queries::users::user_email_changes::list_user_email_changes_f use tokens::tokens::users::UserToken; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_moderator::require_moderator; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; const CURSOR_NAME: &str = "modemchg"; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_feature_flags/moderator_edit_user_feature_flags_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_feature_flags/moderator_edit_user_feature_flags_handler.rs index 80508693f5..b405e10bda 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_feature_flags/moderator_edit_user_feature_flags_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_feature_flags/moderator_edit_user_feature_flags_handler.rs @@ -26,7 +26,7 @@ use tokens::tokens::users::UserToken; use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; -use crate::http_server::web_utils::user_session::require_moderator::require_moderator; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; use artcraft_api_defs::common::responses::simple_generic_json_success::SimpleGenericJsonSuccess; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_feature_flags/moderator_list_all_available_user_feature_flags_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_feature_flags/moderator_list_all_available_user_feature_flags_handler.rs index 279ce42797..230d4d2213 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_feature_flags/moderator_list_all_available_user_feature_flags_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_feature_flags/moderator_list_all_available_user_feature_flags_handler.rs @@ -8,7 +8,7 @@ use utoipa::ToSchema; use enums::by_table::users::user_feature_flag::UserFeatureFlag; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_moderator::require_moderator; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; // ── Response ── diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_feature_flags/moderator_list_user_feature_flags_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_feature_flags/moderator_list_user_feature_flags_handler.rs index 7f5becf2a4..ecade330cb 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_feature_flags/moderator_list_user_feature_flags_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_feature_flags/moderator_list_user_feature_flags_handler.rs @@ -12,7 +12,7 @@ use tokens::tokens::users::UserToken; use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; -use crate::http_server::web_utils::user_session::require_moderator::require_moderator; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; // ── Request ── diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_referrals/moderator_list_global_user_referrals_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_referrals/moderator_list_global_user_referrals_handler.rs index 24b5909236..7602df67bf 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_referrals/moderator_list_global_user_referrals_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_referrals/moderator_list_global_user_referrals_handler.rs @@ -13,7 +13,7 @@ use mysql_queries::queries::user_referrals::list_global_user_referrals::{ }; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_moderator::require_moderator; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; const CURSOR_NAME: &str = "modusrref"; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_referrals/moderator_list_user_referrals_for_user_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_referrals/moderator_list_user_referrals_for_user_handler.rs index dcfc2da2b1..6b22e3a18a 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_referrals/moderator_list_user_referrals_for_user_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_referrals/moderator_list_user_referrals_for_user_handler.rs @@ -16,7 +16,7 @@ use mysql_queries::queries::user_referrals::list_user_referrals_for_user::{ }; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_moderator::require_moderator; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; const CURSOR_NAME: &str = "modusrrefu"; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_sessions/moderator_list_user_session_impersonation_requests_for_user_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_sessions/moderator_list_user_session_impersonation_requests_for_user_handler.rs index 725483aa8d..71ef7f3225 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_sessions/moderator_list_user_session_impersonation_requests_for_user_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_sessions/moderator_list_user_session_impersonation_requests_for_user_handler.rs @@ -12,7 +12,7 @@ use mysql_queries::queries::user_impersonation_requests::list_user_impersonation use tokens::tokens::users::UserToken; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_moderator::require_moderator; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; const CURSOR_NAME: &str = "moduserimp"; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_sessions/moderator_list_user_session_impersonation_requests_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_sessions/moderator_list_user_session_impersonation_requests_handler.rs index 9b6634fdfe..0ddbd5395d 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_sessions/moderator_list_user_session_impersonation_requests_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_sessions/moderator_list_user_session_impersonation_requests_handler.rs @@ -12,7 +12,7 @@ use mysql_queries::queries::user_impersonation_requests::list_user_impersonation use tokens::tokens::users::UserToken; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_moderator::require_moderator; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; const CURSOR_NAME: &str = "modallimp"; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_sessions/moderator_user_session_impersonation_request_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_sessions/moderator_user_session_impersonation_request_handler.rs index 9800d28adb..a34086d2d9 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_sessions/moderator_user_session_impersonation_request_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_sessions/moderator_user_session_impersonation_request_handler.rs @@ -25,7 +25,7 @@ use mysql_queries::queries::users::user::get::lookup_user_for_moderation::{ }; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_moderator::require_moderator; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; // --- Request --- diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_stripe_data/moderator_get_user_stripe_customer_ids_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_stripe_data/moderator_get_user_stripe_customer_ids_handler.rs index 99d390b8bb..485cdbd1a2 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_stripe_data/moderator_get_user_stripe_customer_ids_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_stripe_data/moderator_get_user_stripe_customer_ids_handler.rs @@ -16,7 +16,7 @@ use mysql_queries::queries::users::user_subscriptions::list_stripe_customer_ids_ use tokens::tokens::users::UserToken; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_moderator::require_moderator; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; /// Get all stripe customer ids on file for a user (moderation) diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/wallet_ledger_entries/list_wallet_ledger_entries_by_wallet_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/wallet_ledger_entries/list_wallet_ledger_entries_by_wallet_handler.rs index 8cbd49e9f4..4f9d9b96d3 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/wallet_ledger_entries/list_wallet_ledger_entries_by_wallet_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/wallet_ledger_entries/list_wallet_ledger_entries_by_wallet_handler.rs @@ -13,7 +13,7 @@ use mysql_queries::queries::wallet_ledger_entries::list_wallet_ledger_entries_by use tokens::tokens::wallets::WalletToken; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_moderator::require_moderator; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; /// List wallet ledger entries by wallet token (moderation) diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/wallet_ledger_entries/moderator_get_wallet_ledger_entry_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/wallet_ledger_entries/moderator_get_wallet_ledger_entry_handler.rs index a54aa586b7..7cc5c95fc6 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/wallet_ledger_entries/moderator_get_wallet_ledger_entry_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/wallet_ledger_entries/moderator_get_wallet_ledger_entry_handler.rs @@ -13,7 +13,7 @@ use mysql_queries::queries::wallet_ledger_entries::get_wallet_ledger_entry_for_m use tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_moderator::require_moderator; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; /// Get a single wallet ledger entry by token (moderation) diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/wallets/list_user_wallets_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/wallets/list_user_wallets_handler.rs index 25d09d08c3..4403e06cdb 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/wallets/list_user_wallets_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/wallets/list_user_wallets_handler.rs @@ -13,7 +13,7 @@ use mysql_queries::queries::wallets::list_user_wallets_for_moderation::list_user use tokens::tokens::users::UserToken; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_moderator::require_moderator; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; /// List wallets for a user (moderation) diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/wallets/moderator_add_banked_balance_to_wallet_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/wallets/moderator_add_banked_balance_to_wallet_handler.rs index 1a502c8598..a1939c58b7 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/wallets/moderator_add_banked_balance_to_wallet_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/wallets/moderator_add_banked_balance_to_wallet_handler.rs @@ -21,7 +21,7 @@ use mysql_queries::queries::wallets::add_durable_banked_balance_to_wallet::add_d use tokens::tokens::wallets::WalletToken; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_moderator::require_moderator; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; /// Add banked credits to a wallet (moderation) diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/wallets/moderator_create_wallet_for_user_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/wallets/moderator_create_wallet_for_user_handler.rs index 48123baab8..d790753ddd 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/wallets/moderator_create_wallet_for_user_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/wallets/moderator_create_wallet_for_user_handler.rs @@ -15,7 +15,7 @@ use mysql_queries::queries::wallets::create_new_wallet_for_owner_user::create_ne use mysql_queries::queries::wallets::find_primary_wallet_token_for_owner::find_primary_wallet_token_for_owner_using_connection; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_moderator::require_moderator; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; /// Create a wallet for a user (moderation) diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/wallets/moderator_get_wallet_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/wallets/moderator_get_wallet_handler.rs index 216a7884ef..0f6b2a718c 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/wallets/moderator_get_wallet_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/wallets/moderator_get_wallet_handler.rs @@ -13,7 +13,7 @@ use mysql_queries::queries::wallets::get_wallet_for_moderation::get_wallet_for_m use tokens::tokens::wallets::WalletToken; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_moderator::require_moderator; +use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; /// Get a single wallet by token (moderation) diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tags/set_tags_for_entity_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tags/set_tags_for_entity_handler.rs index f181e7efc4..4fe1e910d5 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tags/set_tags_for_entity_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tags/set_tags_for_entity_handler.rs @@ -9,7 +9,7 @@ use std::sync::Arc; use crate::http_server::common_responses::tag_info::TagInfo; use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; -use crate::http_server::web_utils::user_session::require_user_session_extended::require_user_session_extended; +use crate::http_server::user_lookup::user_session::require_user_session_extended::require_user_session_extended; use crate::state::server_state::ServerState; use actix_web::error::ResponseError; use actix_web::http::StatusCode; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/users/change_password_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/users/change_password_handler.rs index 7f503c932b..7c5b42ec58 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/users/change_password_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/users/change_password_handler.rs @@ -11,7 +11,7 @@ use log::warn; use artcraft_api_defs::users::change_password::{ChangePasswordRequest, ChangePasswordResponse}; use crate::http_server::validations::validate_passwords::validate_passwords; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_user_session_extended::require_user_session_extended; +use crate::http_server::user_lookup::user_session::require_user_session_extended::require_user_session_extended; use crate::state::server_state::ServerState; use http_server_common::request::get_request_ip::get_request_ip; use mysql_queries::queries::users::user::update::update_password::{update_password, UpdatePasswordArgs}; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/users/edit_email_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/users/edit_email_handler.rs index 09886986ab..2b3d84f018 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/users/edit_email_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/users/edit_email_handler.rs @@ -10,7 +10,7 @@ use actix_web::{web, HttpRequest}; use log::warn; use artcraft_api_defs::users::edit_email::{EditEmailRequest, EditEmailResponse}; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_user_session_extended::require_user_session_extended; +use crate::http_server::user_lookup::user_session::require_user_session_extended::require_user_session_extended; use crate::state::server_state::ServerState; use http_server_common::request::get_request_ip::get_request_ip; use mysql_queries::queries::users::user::update::update_email::{update_email, UpdateEmailArgs, UpdateEmailError}; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/users/edit_username_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/users/edit_username_handler.rs index 23cc23b90e..1b8980b3a5 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/users/edit_username_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/users/edit_username_handler.rs @@ -12,7 +12,7 @@ use artcraft_api_defs::users::edit_username::{EditUsernameRequest, EditUsernameR use crate::http_server::validations::is_reserved_username::is_reserved_username; use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::http_server::validations::validate_username::validate_username; -use crate::http_server::web_utils::user_session::require_user_session_extended::require_user_session_extended; +use crate::http_server::user_lookup::user_session::require_user_session_extended::require_user_session_extended; use crate::state::server_state::ServerState; use http_server_common::request::get_request_ip::get_request_ip; use mysql_queries::queries::users::user::update::update_username::{update_username, UpdateUsernameArgs, UpdateUsernameError}; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/update/update_weight_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/update/update_weight_handler.rs index c016f2543a..90a1c89e7b 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/update/update_weight_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/update/update_weight_handler.rs @@ -9,7 +9,7 @@ use utoipa::ToSchema; use crate::configs::supported_languages_for_models::{get_canonicalized_language_tag_for_model, get_primary_language_subtag}; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::web_utils::user_session::require_user_session_extended::require_user_session_extended; +use crate::http_server::user_lookup::user_session::require_user_session_extended::require_user_session_extended; use crate::state::server_state::ServerState; use enums::by_table::media_files::media_file_type::MediaFileType; use enums::by_table::model_weights::weights_types::WeightsType; diff --git a/crates/service/web/storyteller_web/src/http_server/user_lookup/mod.rs b/crates/service/web/storyteller_web/src/http_server/user_lookup/mod.rs index 02a4a96543..0043e41ef5 100644 --- a/crates/service/web/storyteller_web/src/http_server/user_lookup/mod.rs +++ b/crates/service/web/storyteller_web/src/http_server/user_lookup/mod.rs @@ -1 +1,2 @@ pub mod api_keys; +pub mod user_session; diff --git a/crates/service/web/storyteller_web/src/http_server/web_utils/user_session/mod.rs b/crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/mod.rs similarity index 100% rename from crates/service/web/storyteller_web/src/http_server/web_utils/user_session/mod.rs rename to crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/mod.rs diff --git a/crates/service/web/storyteller_web/src/http_server/web_utils/user_session/require_moderator.rs b/crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/require_moderator.rs similarity index 100% rename from crates/service/web/storyteller_web/src/http_server/web_utils/user_session/require_moderator.rs rename to crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/require_moderator.rs diff --git a/crates/service/web/storyteller_web/src/http_server/web_utils/user_session/require_user_session.rs b/crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/require_user_session.rs similarity index 100% rename from crates/service/web/storyteller_web/src/http_server/web_utils/user_session/require_user_session.rs rename to crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/require_user_session.rs diff --git a/crates/service/web/storyteller_web/src/http_server/web_utils/user_session/require_user_session_extended.rs b/crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/require_user_session_extended.rs similarity index 100% rename from crates/service/web/storyteller_web/src/http_server/web_utils/user_session/require_user_session_extended.rs rename to crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/require_user_session_extended.rs diff --git a/crates/service/web/storyteller_web/src/http_server/web_utils/mod.rs b/crates/service/web/storyteller_web/src/http_server/web_utils/mod.rs index 2617673672..7e78273885 100644 --- a/crates/service/web/storyteller_web/src/http_server/web_utils/mod.rs +++ b/crates/service/web/storyteller_web/src/http_server/web_utils/mod.rs @@ -12,4 +12,3 @@ pub mod response_success_helpers; pub mod scoped_temp_dir_creator; pub mod serialize_as_json_error; pub mod try_delete_session_cache; -pub mod user_session; \ No newline at end of file From 0b4e288a3e9fe4dac06de8586b131cb44653e2da Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Tue, 16 Jun 2026 01:53:13 -0400 Subject: [PATCH 12/53] refactor another module location --- .../src/billing/internal_session_cache_purge_impl.rs | 2 +- .../src/billing/stripe_internal_user_lookup_impl.rs | 2 +- .../src/configs/plans/get_correct_plan_for_session.rs | 4 ++-- .../src/http_server/common_responses/common_web_error.rs | 2 +- .../animation/enqueue_face_animation.rs | 2 +- .../animation/enqueue_rerender_animation.rs | 2 +- .../media_uploads/list_user_media_uploads_of_type.rs | 2 +- .../deprecated_endpoints/mocap/enqueue_mocapnet.rs | 2 +- .../workflows/enqueue/enqueue_studio_workflow_handler.rs | 2 +- .../enqueue_video_style_transfer_workflow_handler.rs | 2 +- .../workflows/enqueue_comfy_ui_handler.rs | 2 +- .../workflows/enqueue_video_style_transfer_handler.rs | 2 +- .../endpoints/api_keys/create_api_key_handler.rs | 2 +- .../endpoints/app_state/components/get_permissions.rs | 2 +- .../endpoints/app_state/components/get_premium_info.rs | 2 +- .../endpoints/app_state/components/get_user_info.rs | 2 +- .../endpoints/beta_keys/redeem_beta_key_handler.rs | 2 +- .../seedance_2p0_multi_function_video_gen_handler.rs | 2 +- .../moderator_edit_user_feature_flags_handler.rs | 2 +- .../moderator_list_user_feature_flags_handler.rs | 2 +- .../generate/image/omni_api_image_generate_handler.rs | 2 +- .../generate/video/omni_api_video_generate_handler.rs | 2 +- .../generate/image/omni_gen_image_generate_handler.rs | 2 +- .../generate/video/omni_gen_video_generate_handler.rs | 2 +- .../omni_gen/models/video/omni_gen_video_models_handler.rs | 2 +- .../endpoints/studio_gen2/enqueue_studio_gen2_handler.rs | 2 +- .../enqueue_infer_f5_tts_handler.rs | 2 +- .../enqueue_infer_tts_handler/enqueue_infer_tts_handler.rs | 2 +- .../user_referral_codes/create_referral_code_handler.rs | 2 +- .../src/http_server/endpoints/users/edit_profile_handler.rs | 2 +- .../src/http_server/endpoints/users/get_profile_handler.rs | 4 ++-- .../users/google_sso/handle_new_sso_account_for_new_user.rs | 2 +- .../src/http_server/endpoints/users/login_handler.rs | 2 +- .../src/http_server/endpoints/users/session_info_handler.rs | 4 ++-- .../endpoints/users/session_token_info_handler.rs | 2 +- .../voice_conversion/enqueue_seed_vc_inference_handler.rs | 2 +- crates/service/web/storyteller_web/src/http_server/mod.rs | 1 - .../src/http_server/user_lookup/user_session/mod.rs | 1 + .../user_lookup/user_session/require_moderator.rs | 2 +- .../user_lookup/user_session/require_user_session.rs | 2 +- .../user_session/require_user_session_extended.rs | 4 ++-- .../user_session/session_utils}/lookup/mod.rs | 0 .../session_utils}/lookup/user_session_extended.rs | 2 +- .../session_utils}/lookup/user_session_feature_flags.rs | 2 +- .../user_session/session_utils}/mod.rs | 0 .../user_session/session_utils}/session_checker.rs | 6 +++--- .../user_session/session_utils}/session_checker_error.rs | 0 .../web/storyteller_web/src/startup/build_dependencies.rs | 2 +- .../service/web/storyteller_web/src/state/server_state.rs | 2 +- .../src/util/allowed_explore_media_access.rs | 4 ++-- .../web/storyteller_web/src/util/allowed_studio_access.rs | 4 ++-- .../src/util/allowed_video_style_transfer_access.rs | 4 ++-- .../web/storyteller_web/src/util/enroll_in_studio.rs | 2 +- 53 files changed, 58 insertions(+), 58 deletions(-) rename crates/service/web/storyteller_web/src/http_server/{session => user_lookup/user_session/session_utils}/lookup/mod.rs (100%) rename crates/service/web/storyteller_web/src/http_server/{session => user_lookup/user_session/session_utils}/lookup/user_session_extended.rs (96%) rename crates/service/web/storyteller_web/src/http_server/{session => user_lookup/user_session/session_utils}/lookup/user_session_feature_flags.rs (98%) rename crates/service/web/storyteller_web/src/http_server/{session => user_lookup/user_session/session_utils}/mod.rs (100%) rename crates/service/web/storyteller_web/src/http_server/{session => user_lookup/user_session/session_utils}/session_checker.rs (96%) rename crates/service/web/storyteller_web/src/http_server/{session => user_lookup/user_session/session_utils}/session_checker_error.rs (100%) diff --git a/crates/service/web/storyteller_web/src/billing/internal_session_cache_purge_impl.rs b/crates/service/web/storyteller_web/src/billing/internal_session_cache_purge_impl.rs index 0ce99a31cc..bad5300e09 100644 --- a/crates/service/web/storyteller_web/src/billing/internal_session_cache_purge_impl.rs +++ b/crates/service/web/storyteller_web/src/billing/internal_session_cache_purge_impl.rs @@ -3,7 +3,7 @@ use actix_web::HttpRequest; use redis_caching::redis_ttl_cache::RedisTtlCache; use redis_common::redis_cache_keys::RedisCacheKeys; use user_traits_component::traits::internal_session_cache_purge::InternalSessionCachePurge; -use crate::http_server::session::session_checker::SessionChecker; +use crate::http_server::user_lookup::user_session::session_utils::session_checker::SessionChecker; pub struct InternalSessionCachePurgeImpl { session_checker: SessionChecker, diff --git a/crates/service/web/storyteller_web/src/billing/stripe_internal_user_lookup_impl.rs b/crates/service/web/storyteller_web/src/billing/stripe_internal_user_lookup_impl.rs index 21b88ece3e..112d38be06 100644 --- a/crates/service/web/storyteller_web/src/billing/stripe_internal_user_lookup_impl.rs +++ b/crates/service/web/storyteller_web/src/billing/stripe_internal_user_lookup_impl.rs @@ -1,4 +1,4 @@ -use crate::http_server::session::session_checker::SessionChecker; +use crate::http_server::user_lookup::user_session::session_utils::session_checker::SessionChecker; use actix_web::HttpRequest; use async_trait::async_trait; use chrono::Utc; diff --git a/crates/service/web/storyteller_web/src/configs/plans/get_correct_plan_for_session.rs b/crates/service/web/storyteller_web/src/configs/plans/get_correct_plan_for_session.rs index ba270e4e08..53d03d4c8b 100644 --- a/crates/service/web/storyteller_web/src/configs/plans/get_correct_plan_for_session.rs +++ b/crates/service/web/storyteller_web/src/configs/plans/get_correct_plan_for_session.rs @@ -5,7 +5,7 @@ use server_environment::ServerEnvironment; use crate::configs::plans::plan::Plan; use crate::configs::plans::plan_list::{DEVELOPMENT_PREMIUM_PLANS_BY_SLUG, FREE_LOGGED_IN_PLAN, FREE_LOGGED_OUT_PLAN, LOYALTY_PLANS_BY_SLUG, PRODUCTION_PREMIUM_PLANS_BY_SLUG}; -use crate::http_server::session::lookup::user_session_extended::UserSessionExtended; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_extended::UserSessionExtended; /// Look up the most appropriate plan for the session. /// This will probably grow to include a lot of factors. @@ -70,7 +70,7 @@ pub mod tests { use crate::configs::plans::get_correct_plan_for_session::get_correct_plan_for_session; use crate::configs::plans::plan_list::{ALL_PLANS_BY_SLUG, FREE_LOGGED_IN_PLAN, FREE_LOGGED_OUT_PLAN}; - use crate::http_server::session::lookup::user_session_extended::{UserSessionExtended, UserSessionSubscriptionPlan}; + use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_extended::{UserSessionExtended, UserSessionSubscriptionPlan}; #[test] fn test_free_logged_out_plan() { diff --git a/crates/service/web/storyteller_web/src/http_server/common_responses/common_web_error.rs b/crates/service/web/storyteller_web/src/http_server/common_responses/common_web_error.rs index 30e479da1d..5950132f59 100644 --- a/crates/service/web/storyteller_web/src/http_server/common_responses/common_web_error.rs +++ b/crates/service/web/storyteller_web/src/http_server/common_responses/common_web_error.rs @@ -1,7 +1,7 @@ use std::fmt::{Display, Formatter}; use std::sync::Arc; -use crate::http_server::session::session_checker_error::SessionCheckerError; +use crate::http_server::user_lookup::user_session::session_utils::session_checker_error::SessionCheckerError; use actix_artcraft::sessions::anonymous_visitor_tracking::avt_cookie_payload_error::AvtCookiePayloadError; use actix_artcraft::sessions::user_sessions::http_user_session_payload_error::HttpUserSessionPayloadError; use actix_http::StatusCode; diff --git a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/animation/enqueue_face_animation.rs b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/animation/enqueue_face_animation.rs index 5ab75450df..4640768fe0 100644 --- a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/animation/enqueue_face_animation.rs +++ b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/animation/enqueue_face_animation.rs @@ -28,7 +28,7 @@ use tokens::tokens::voice_conversion_results::VoiceConversionResultToken; use crate::configs::plans::get_correct_plan_for_session::get_correct_plan_for_session; use crate::http_server::requests::request_headers::get_routing_tag_header::get_routing_tag_header; use crate::http_server::requests::request_headers::has_debug_header::has_debug_header; -use crate::http_server::session::lookup::user_session_extended::UserSessionExtended; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_extended::UserSessionExtended; use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; use crate::state::server_state::ServerState; diff --git a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/animation/enqueue_rerender_animation.rs b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/animation/enqueue_rerender_animation.rs index 2cf1a7f268..a4aec6e1b7 100644 --- a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/animation/enqueue_rerender_animation.rs +++ b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/animation/enqueue_rerender_animation.rs @@ -10,7 +10,7 @@ use actix_web::http::StatusCode; use actix_web::{web, HttpRequest, HttpResponse}; use log::{info, warn}; -use crate::http_server::session::lookup::user_session_extended::UserSessionExtended; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_extended::UserSessionExtended; use enums::by_table::generic_inference_jobs::inference_category::InferenceCategory; use enums::by_table::generic_inference_jobs::inference_job_type::InferenceJobType; use enums::by_table::generic_inference_jobs::inference_model_type::InferenceModelType; diff --git a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/media_uploads/list_user_media_uploads_of_type.rs b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/media_uploads/list_user_media_uploads_of_type.rs index 5df5d6218c..33f128c399 100644 --- a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/media_uploads/list_user_media_uploads_of_type.rs +++ b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/media_uploads/list_user_media_uploads_of_type.rs @@ -8,7 +8,7 @@ use actix_web::{web, HttpMessage, HttpRequest, HttpResponse}; use chrono::{DateTime, Utc}; use log::warn; -use crate::http_server::session::lookup::user_session_extended::UserSessionExtended; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_extended::UserSessionExtended; use crate::http_server::common_responses::common_web_error::CommonWebError; use enums::by_table::media_uploads::media_upload_type::MediaUploadType; use enums::common::visibility::Visibility; diff --git a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/mocap/enqueue_mocapnet.rs b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/mocap/enqueue_mocapnet.rs index b1ec67e92b..0c1e4ad235 100644 --- a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/mocap/enqueue_mocapnet.rs +++ b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/mocap/enqueue_mocapnet.rs @@ -10,7 +10,7 @@ use actix_web::http::StatusCode; use actix_web::{web, HttpRequest, HttpResponse}; use log::{error, info, warn}; -use crate::http_server::session::lookup::user_session_extended::UserSessionExtended; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_extended::UserSessionExtended; use enums::by_table::generic_inference_jobs::inference_category::InferenceCategory; use enums::by_table::generic_inference_jobs::inference_job_type::InferenceJobType; use enums::by_table::generic_inference_jobs::inference_model_type::InferenceModelType; diff --git a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue/enqueue_studio_workflow_handler.rs b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue/enqueue_studio_workflow_handler.rs index 9a08810a9b..1160309f4b 100644 --- a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue/enqueue_studio_workflow_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue/enqueue_studio_workflow_handler.rs @@ -36,7 +36,7 @@ use crate::http_server::deprecated_endpoints::workflows::enqueue::vst_common::vs use crate::http_server::deprecated_endpoints::workflows::enqueue::vst_common::vst_response::VstSuccessResponse; use crate::http_server::requests::request_headers::get_routing_tag_header::get_routing_tag_header; use crate::http_server::requests::request_headers::has_debug_header::has_debug_header; -use crate::http_server::session::lookup::user_session_extended::UserSessionExtended; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_extended::UserSessionExtended; use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; use crate::state::server_state::ServerState; use crate::util::allowed_video_style_transfer_access::allowed_video_style_transfer_access; diff --git a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue/enqueue_video_style_transfer_workflow_handler.rs b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue/enqueue_video_style_transfer_workflow_handler.rs index 1fa1a56b90..d2cb340ff5 100644 --- a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue/enqueue_video_style_transfer_workflow_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue/enqueue_video_style_transfer_workflow_handler.rs @@ -37,7 +37,7 @@ use crate::http_server::deprecated_endpoints::workflows::enqueue::vst_common::vs use crate::http_server::requests::get_request_domain_branding::{get_request_domain_branding, DomainBranding}; use crate::http_server::requests::request_headers::get_routing_tag_header::get_routing_tag_header; use crate::http_server::requests::request_headers::has_debug_header::has_debug_header; -use crate::http_server::session::lookup::user_session_extended::UserSessionExtended; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_extended::UserSessionExtended; use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; use crate::state::server_state::ServerState; use crate::util::allowed_video_style_transfer_access::allowed_video_style_transfer_access; diff --git a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue_comfy_ui_handler.rs b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue_comfy_ui_handler.rs index 60fd6bf04b..bdf2ac943d 100644 --- a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue_comfy_ui_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue_comfy_ui_handler.rs @@ -32,7 +32,7 @@ use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::configs::plans::plan_category::PlanCategory; use crate::http_server::requests::request_headers::get_routing_tag_header::get_routing_tag_header; use crate::http_server::requests::request_headers::has_debug_header::has_debug_header; -use crate::http_server::session::lookup::user_session_extended::UserSessionExtended; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_extended::UserSessionExtended; use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; use crate::state::server_state::ServerState; use crate::util::allowed_video_style_transfer_access::allowed_video_style_transfer_access; diff --git a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue_video_style_transfer_handler.rs b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue_video_style_transfer_handler.rs index 1eeb8cc71c..eeacbde794 100644 --- a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue_video_style_transfer_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue_video_style_transfer_handler.rs @@ -33,7 +33,7 @@ use crate::configs::plans::plan_category::PlanCategory; use crate::http_server::deprecated_endpoints::workflows::coordinate_workflow_args::{coordinate_workflow_args, CoordinatedWorkflowArgs}; use crate::http_server::requests::request_headers::get_routing_tag_header::get_routing_tag_header; use crate::http_server::requests::request_headers::has_debug_header::has_debug_header; -use crate::http_server::session::lookup::user_session_extended::UserSessionExtended; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_extended::UserSessionExtended; use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; use crate::state::server_state::ServerState; use crate::util::allowed_video_style_transfer_access::allowed_video_style_transfer_access; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/create_api_key_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/create_api_key_handler.rs index 5500f3e515..17ef253968 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/create_api_key_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/api_keys/create_api_key_handler.rs @@ -10,7 +10,7 @@ use http_server_common::request::get_request_ip::get_request_ip; use mysql_queries::queries::api_keys::insert_api_key::{insert_api_key, InsertApiKeyArgs}; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_feature_flags::UserSessionFeatureFlags; use crate::http_server::user_lookup::user_session::require_user_session::require_user_session; use crate::state::server_state::ServerState; use crate::util::generate_api_key::generate_api_key; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/app_state/components/get_permissions.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/app_state/components/get_permissions.rs index 03576c44a7..46acef0c38 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/app_state/components/get_permissions.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/app_state/components/get_permissions.rs @@ -1,4 +1,4 @@ -use crate::http_server::session::lookup::user_session_extended::{UserSessionExtended, UserSessionRoleAndPermissions}; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_extended::{UserSessionExtended, UserSessionRoleAndPermissions}; use enums::by_table::users::user_feature_flag::UserFeatureFlag; use std::collections::BTreeSet; use utoipa::ToSchema; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/app_state/components/get_premium_info.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/app_state/components/get_premium_info.rs index e1e0bf5b73..e7e1dec145 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/app_state/components/get_premium_info.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/app_state/components/get_premium_info.rs @@ -1,4 +1,4 @@ -use crate::http_server::session::lookup::user_session_extended::UserSessionExtended; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_extended::UserSessionExtended; use utoipa::ToSchema; #[derive(Serialize, ToSchema)] diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/app_state/components/get_user_info.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/app_state/components/get_user_info.rs index fcc5d8d322..6c4ed495a7 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/app_state/components/get_user_info.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/app_state/components/get_user_info.rs @@ -1,5 +1,5 @@ use crate::http_server::common_responses::user_details_lite::UserDefaultAvatarInfo; -use crate::http_server::session::lookup::user_session_extended::UserSessionExtended; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_extended::UserSessionExtended; use tokens::tokens::users::UserToken; use utoipa::ToSchema; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/redeem_beta_key_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/redeem_beta_key_handler.rs index ef87ab27cf..43a1562ffd 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/redeem_beta_key_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/redeem_beta_key_handler.rs @@ -6,7 +6,7 @@ use log::warn; use utoipa::ToSchema; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_feature_flags::UserSessionFeatureFlags; use enums::by_table::beta_keys::beta_key_product::BetaKeyProduct; use enums::by_table::users::user_feature_flag::UserFeatureFlag; use http_server_common::request::get_request_ip::get_request_ip; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/generate/video/multi_function/seedance_2p0_multi_function_video_gen_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/generate/video/multi_function/seedance_2p0_multi_function_video_gen_handler.rs index 2fd1b5f71b..c73ba5db45 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/generate/video/multi_function/seedance_2p0_multi_function_video_gen_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/generate/video/multi_function/seedance_2p0_multi_function_video_gen_handler.rs @@ -6,7 +6,7 @@ use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::http_server::endpoint_helpers::refund_wallet_after_api_failure::refund_wallet_after_api_failure; use crate::http_server::endpoints::generate::common::map_seedance2pro_web_errors::map_seedance2pro_error_to_web_error; use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test; -use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_feature_flags::UserSessionFeatureFlags; use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; use crate::http_server::web_utils::get_request_platform_type::get_request_platform_type; use crate::state::server_state::ServerState; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_feature_flags/moderator_edit_user_feature_flags_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_feature_flags/moderator_edit_user_feature_flags_handler.rs index b405e10bda..d0c60b0929 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_feature_flags/moderator_edit_user_feature_flags_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_feature_flags/moderator_edit_user_feature_flags_handler.rs @@ -23,7 +23,7 @@ use mysql_queries::queries::users::user::update::set_user_feature_flags::{set_us use mysql_queries::queries::users::user_profiles::get_user_profile_by_token::get_user_profile_by_token; use tokens::tokens::users::UserToken; -use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_feature_flags::UserSessionFeatureFlags; use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_feature_flags/moderator_list_user_feature_flags_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_feature_flags/moderator_list_user_feature_flags_handler.rs index ecade330cb..5c7b5442d4 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_feature_flags/moderator_list_user_feature_flags_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user_feature_flags/moderator_list_user_feature_flags_handler.rs @@ -11,7 +11,7 @@ use mysql_queries::queries::users::user_profiles::get_user_profile_by_token::get use tokens::tokens::users::UserToken; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_feature_flags::UserSessionFeatureFlags; use crate::http_server::user_lookup::user_session::require_moderator::require_moderator; use crate::state::server_state::ServerState; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/omni_api_image_generate_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/omni_api_image_generate_handler.rs index 77811495f8..ff52603ba1 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/omni_api_image_generate_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/image/omni_api_image_generate_handler.rs @@ -33,7 +33,7 @@ use crate::http_server::endpoints::omni_api::generate::image::insert_db_job::ins use crate::http_server::endpoints::omni_api::generate::image::insert_db_job::insert_seedance2pro_jobs::{insert_seedance2pro_jobs, InsertSeedance2proJobsArgs}; use crate::http_server::endpoints::omni_api::generate::image::insert_db_job::shared_job_args::SharedJobArgs; use crate::http_server::endpoints::omni_api::generate::image::pipeline_v2::run_pipeline_v2::{run_pipeline_v2, RunPipelineV2Args}; -use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_feature_flags::UserSessionFeatureFlags; use crate::http_server::user_lookup::api_keys::require_api_key_user::require_api_key_user; use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; use crate::state::server_state::ServerState; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/omni_api_video_generate_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/omni_api_video_generate_handler.rs index 1594305c47..c7f3ae5a7c 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/omni_api_video_generate_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/omni_api_video_generate_handler.rs @@ -42,7 +42,7 @@ use crate::http_server::endpoints::omni_api::generate::video::insert_db_job::sha use crate::http_server::endpoints::omni_api::generate::video::kinovi_account::KinoviAccount; use crate::http_server::endpoints::omni_api::generate::video::pipeline_v2::run_pipeline_v2::{run_pipeline_v2, RunPipelineV2Args}; use crate::http_server::endpoints::omni_api::shared_utils::video::validate_video_request::validate_video_request; -use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_feature_flags::UserSessionFeatureFlags; use crate::http_server::user_lookup::api_keys::require_api_key_user::require_api_key_user; use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; use crate::state::server_state::ServerState; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/image/omni_gen_image_generate_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/image/omni_gen_image_generate_handler.rs index c874a4fb71..94abe1ee5f 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/image/omni_gen_image_generate_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/image/omni_gen_image_generate_handler.rs @@ -32,7 +32,7 @@ use crate::http_server::endpoints::omni_gen::generate::image::insert_db_job::ins use crate::http_server::endpoints::omni_gen::generate::image::insert_db_job::insert_seedance2pro_jobs::{insert_seedance2pro_jobs, InsertSeedance2proJobsArgs}; use crate::http_server::endpoints::omni_gen::generate::image::insert_db_job::shared_job_args::SharedJobArgs; use crate::http_server::endpoints::omni_gen::generate::image::pipeline_v2::run_pipeline_v2::{run_pipeline_v2, RunPipelineV2Args}; -use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_feature_flags::UserSessionFeatureFlags; use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; use crate::http_server::web_utils::get_request_platform_type::get_request_platform_type; use crate::state::server_state::ServerState; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/omni_gen_video_generate_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/omni_gen_video_generate_handler.rs index 9824680a02..40f867d6e0 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/omni_gen_video_generate_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/omni_gen_video_generate_handler.rs @@ -41,7 +41,7 @@ use crate::http_server::endpoints::omni_gen::generate::video::insert_db_job::sha use crate::http_server::endpoints::omni_gen::generate::video::kinovi_account::KinoviAccount; use crate::http_server::endpoints::omni_gen::generate::video::pipeline_v2::run_pipeline_v2::{run_pipeline_v2, RunPipelineV2Args}; use crate::http_server::endpoints::omni_gen::shared_utils::video::validate_video_request::validate_video_request; -use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_feature_flags::UserSessionFeatureFlags; use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; use crate::http_server::web_utils::get_request_platform_type::get_request_platform_type; use crate::state::server_state::ServerState; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/models/video/omni_gen_video_models_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/models/video/omni_gen_video_models_handler.rs index 9769eb23af..d50f5f7c48 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/models/video/omni_gen_video_models_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/models/video/omni_gen_video_models_handler.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use crate::configs::omni_gen::video_models::OMNI_GEN_VIDEO_MODELS_AND_PROVIDERS; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_feature_flags::UserSessionFeatureFlags; use crate::state::server_state::ServerState; use actix_web::web::{Json, Query}; use actix_web::{web, HttpRequest}; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/studio_gen2/enqueue_studio_gen2_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/studio_gen2/enqueue_studio_gen2_handler.rs index d36e35375a..5c976c6a51 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/studio_gen2/enqueue_studio_gen2_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/studio_gen2/enqueue_studio_gen2_handler.rs @@ -30,7 +30,7 @@ use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::configs::plans::plan_category::PlanCategory; use crate::http_server::requests::request_headers::get_routing_tag_header::get_routing_tag_header; use crate::http_server::requests::request_headers::has_debug_header::has_debug_header; -use crate::http_server::session::lookup::user_session_extended::UserSessionExtended; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_extended::UserSessionExtended; use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; use crate::state::server_state::ServerState; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_infer_f5_tts_handler/enqueue_infer_f5_tts_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_infer_f5_tts_handler/enqueue_infer_f5_tts_handler.rs index db07766eed..925aa3002e 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_infer_f5_tts_handler/enqueue_infer_f5_tts_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_infer_f5_tts_handler/enqueue_infer_f5_tts_handler.rs @@ -37,7 +37,7 @@ use crate::configs::app_startup::username_set::UsernameSet; use crate::configs::plans::get_correct_plan_for_session::get_correct_plan_for_session; use crate::configs::plans::plan::Plan; use crate::http_server::deprecated_endpoints::investor_demo::demo_cookie::request_has_demo_cookie; -use crate::http_server::session::lookup::user_session_extended::UserSessionExtended; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_extended::UserSessionExtended; use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; use crate::state::server_state::ServerState; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_infer_tts_handler/enqueue_infer_tts_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_infer_tts_handler/enqueue_infer_tts_handler.rs index ac186f5ef4..17ff4aa849 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_infer_tts_handler/enqueue_infer_tts_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_infer_tts_handler/enqueue_infer_tts_handler.rs @@ -38,7 +38,7 @@ use crate::configs::plans::get_correct_plan_for_session::get_correct_plan_for_se use crate::configs::plans::plan::Plan; use crate::http_server::deprecated_endpoints::investor_demo::demo_cookie::request_has_demo_cookie; use crate::http_server::endpoints::tts::enqueue_infer_tts_handler::get_tts_model_with_caching::get_tts_model_with_caching; -use crate::http_server::session::lookup::user_session_extended::UserSessionExtended; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_extended::UserSessionExtended; use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; use crate::state::server_state::ServerState; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/user_referral_codes/create_referral_code_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/user_referral_codes/create_referral_code_handler.rs index 1ec2a043a2..14d464da1d 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/user_referral_codes/create_referral_code_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/user_referral_codes/create_referral_code_handler.rs @@ -7,7 +7,7 @@ use utoipa::ToSchema; use artcraft_api_defs::user_referral_codes::create_referral_code::{CreateReferralCodeRequest, CreateReferralCodeResponse}; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_feature_flags::UserSessionFeatureFlags; use crate::state::server_state::ServerState; use mysql_queries::errors::database_insert_error::DatabaseInsertError; use mysql_queries::queries::user_referral_codes::create_referral_code::{create_referral_code, CreateReferralCodeArgs}; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/users/edit_profile_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/users/edit_profile_handler.rs index 6c7a63a675..4cb136a50d 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/users/edit_profile_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/users/edit_profile_handler.rs @@ -21,7 +21,7 @@ use redis_caching::redis_ttl_cache::RedisTtlCache; use redis_common::redis_cache_keys::RedisCacheKeys; use user_input_common::check_for_slurs::contains_slurs; -use crate::http_server::session::session_checker::SessionChecker; +use crate::http_server::user_lookup::user_session::session_utils::session_checker::SessionChecker; use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::http_server::validations::validate_profile_cashapp_username::{normalize_cashapp_username_for_storage, validate_profile_cashapp_username}; use crate::http_server::validations::validate_profile_discord_username::validate_profile_discord_username; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/users/get_profile_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/users/get_profile_handler.rs index 0e97ffedd8..b9b263b046 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/users/get_profile_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/users/get_profile_handler.rs @@ -27,8 +27,8 @@ use crate::http_server::common_responses::user_avatars::default_avatar_color_fro use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::http_server::common_responses::user_avatars::default_avatar_from_username::default_avatar_from_username; use crate::http_server::common_responses::user_details_lite::UserDetailsLight; -use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; -use crate::http_server::session::session_checker::SessionChecker; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_feature_flags::UserSessionFeatureFlags; +use crate::http_server::user_lookup::user_session::session_utils::session_checker::SessionChecker; // TODO: This is duplicated in query_user_profile diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/users/google_sso/handle_new_sso_account_for_new_user.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/users/google_sso/handle_new_sso_account_for_new_user.rs index bce507df70..e099b62b55 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/users/google_sso/handle_new_sso_account_for_new_user.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/users/google_sso/handle_new_sso_account_for_new_user.rs @@ -1,6 +1,6 @@ use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::http_server::endpoints::users::google_sso::handle_new_sso_account::NewSsoAccountInfo; -use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_feature_flags::UserSessionFeatureFlags; use actix_artcraft::requests::get_request_signup_source_enum::get_request_signup_source_enum; use actix_web::HttpRequest; use enums::by_table::users::user_feature_flag::UserFeatureFlag; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/users/login_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/users/login_handler.rs index 64fb92182b..07138b04eb 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/users/login_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/users/login_handler.rs @@ -33,7 +33,7 @@ use utoipa::ToSchema; use artcraft_api_defs::users::login::{LoginErrorType, LoginRequest, LoginSuccessResponse}; use password::errors::password_confirm_error::PasswordConfirmError; use tokens::tokens::users::UserToken; -use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_feature_flags::UserSessionFeatureFlags; use crate::util::enroll_in_studio::enroll_in_studio; #[derive(Serialize, Debug, ToSchema)] diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/users/session_info_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/users/session_info_handler.rs index c3ca810b8a..e34ac72f37 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/users/session_info_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/users/session_info_handler.rs @@ -13,8 +13,8 @@ use enums::by_table::users::user_feature_flag::UserFeatureFlag; use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::http_server::common_responses::user_details_lite_builder::UserDetailsLightBuilder; use actix_artcraft::sessions::anonymous_visitor_tracking::avt_cookie_manager::AvtCookieManager; -use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; -use crate::http_server::session::session_checker::SessionChecker; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_feature_flags::UserSessionFeatureFlags; +use crate::http_server::user_lookup::user_session::session_utils::session_checker::SessionChecker; #[utoipa::path( get, diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/users/session_token_info_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/users/session_token_info_handler.rs index 3b40181675..2925297e7c 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/users/session_token_info_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/users/session_token_info_handler.rs @@ -10,7 +10,7 @@ use utoipa::ToSchema; use http_server_common::response::response_error_helpers::to_simple_json_error; -use crate::http_server::session::session_checker::SessionChecker; +use crate::http_server::user_lookup::user_session::session_utils::session_checker::SessionChecker; use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::state::server_state::ServerState; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_conversion/enqueue_seed_vc_inference_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_conversion/enqueue_seed_vc_inference_handler.rs index 34659709da..6059dee028 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_conversion/enqueue_seed_vc_inference_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_conversion/enqueue_seed_vc_inference_handler.rs @@ -30,7 +30,7 @@ use crate::configs::app_startup::username_set::UsernameSet; use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::configs::plans::get_correct_plan_for_session::get_correct_plan_for_session; use crate::http_server::deprecated_endpoints::investor_demo::demo_cookie::request_has_demo_cookie; -use crate::http_server::session::lookup::user_session_extended::UserSessionExtended; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_extended::UserSessionExtended; use crate::state::server_state::ServerState; // TODO: Temporary for investor demo diff --git a/crates/service/web/storyteller_web/src/http_server/mod.rs b/crates/service/web/storyteller_web/src/http_server/mod.rs index c4aeed0e92..2bc90b5c60 100644 --- a/crates/service/web/storyteller_web/src/http_server/mod.rs +++ b/crates/service/web/storyteller_web/src/http_server/mod.rs @@ -6,7 +6,6 @@ pub mod endpoints; pub mod middleware; pub mod requests; pub mod routes; -pub mod session; pub mod user_lookup; pub mod validations; pub mod web_utils; \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/mod.rs b/crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/mod.rs index 446fa123d5..af5848741f 100644 --- a/crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/mod.rs +++ b/crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/mod.rs @@ -1,3 +1,4 @@ pub mod require_moderator; pub mod require_user_session; pub mod require_user_session_extended; +pub mod session_utils; diff --git a/crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/require_moderator.rs b/crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/require_moderator.rs index 69466b4e1a..b9cb4bb7a1 100644 --- a/crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/require_moderator.rs +++ b/crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/require_moderator.rs @@ -5,7 +5,7 @@ use sqlx::{Executor, MySql}; use mysql_queries::queries::users::user_sessions::get_user_session_by_token::SessionUserRecord; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::session::session_checker::SessionChecker; +use crate::http_server::user_lookup::user_session::session_utils::session_checker::SessionChecker; /// `mysql_executor` can be any sqlx executor — pass `&server_state.mysql_pool` to grab a /// fresh connection, or an in-flight connection/transaction (`&mut *connection`) to reuse diff --git a/crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/require_user_session.rs b/crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/require_user_session.rs index 65cf5d2063..57c48b3bf9 100644 --- a/crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/require_user_session.rs +++ b/crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/require_user_session.rs @@ -5,7 +5,7 @@ use sqlx::{Executor, MySql}; use mysql_queries::queries::users::user_sessions::get_user_session_by_token::SessionUserRecord; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::session::session_checker::SessionChecker; +use crate::http_server::user_lookup::user_session::session_utils::session_checker::SessionChecker; /// `mysql_executor` can be any sqlx executor — pass `&server_state.mysql_pool` to grab a /// fresh connection, or an in-flight connection/transaction (`&mut *connection`) to reuse diff --git a/crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/require_user_session_extended.rs b/crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/require_user_session_extended.rs index 2bc037be10..a4c1fb1791 100644 --- a/crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/require_user_session_extended.rs +++ b/crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/require_user_session_extended.rs @@ -3,8 +3,8 @@ use log::warn; use sqlx::MySqlConnection; use crate::http_server::common_responses::common_web_error::CommonWebError; -use crate::http_server::session::lookup::user_session_extended::UserSessionExtended; -use crate::http_server::session::session_checker::SessionChecker; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_extended::UserSessionExtended; +use crate::http_server::user_lookup::user_session::session_utils::session_checker::SessionChecker; /// Pass an in-flight connection (`&mut *connection`) to reuse one the handler already holds. /// (The extended lookup runs two queries, so it takes a concrete `&mut MySqlConnection` — which diff --git a/crates/service/web/storyteller_web/src/http_server/session/lookup/mod.rs b/crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/session_utils/lookup/mod.rs similarity index 100% rename from crates/service/web/storyteller_web/src/http_server/session/lookup/mod.rs rename to crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/session_utils/lookup/mod.rs diff --git a/crates/service/web/storyteller_web/src/http_server/session/lookup/user_session_extended.rs b/crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/session_utils/lookup/user_session_extended.rs similarity index 96% rename from crates/service/web/storyteller_web/src/http_server/session/lookup/user_session_extended.rs rename to crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/session_utils/lookup/user_session_extended.rs index cb2c56cb61..ee8dcae790 100644 --- a/crates/service/web/storyteller_web/src/http_server/session/lookup/user_session_extended.rs +++ b/crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/session_utils/lookup/user_session_extended.rs @@ -3,7 +3,7 @@ use enums::common::payments_namespace::PaymentsNamespace; use enums::common::visibility::Visibility; use tokens::tokens::users::UserToken; -use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_feature_flags::UserSessionFeatureFlags; #[derive(Clone, Default)] pub struct UserSessionExtended { diff --git a/crates/service/web/storyteller_web/src/http_server/session/lookup/user_session_feature_flags.rs b/crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/session_utils/lookup/user_session_feature_flags.rs similarity index 98% rename from crates/service/web/storyteller_web/src/http_server/session/lookup/user_session_feature_flags.rs rename to crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/session_utils/lookup/user_session_feature_flags.rs index 0f2e711b20..7db883bd1d 100644 --- a/crates/service/web/storyteller_web/src/http_server/session/lookup/user_session_feature_flags.rs +++ b/crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/session_utils/lookup/user_session_feature_flags.rs @@ -113,7 +113,7 @@ mod tests { use enums::by_table::users::user_feature_flag::UserFeatureFlag; - use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; + use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_feature_flags::UserSessionFeatureFlags; mod construction { use super::*; diff --git a/crates/service/web/storyteller_web/src/http_server/session/mod.rs b/crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/session_utils/mod.rs similarity index 100% rename from crates/service/web/storyteller_web/src/http_server/session/mod.rs rename to crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/session_utils/mod.rs diff --git a/crates/service/web/storyteller_web/src/http_server/session/session_checker.rs b/crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/session_utils/session_checker.rs similarity index 96% rename from crates/service/web/storyteller_web/src/http_server/session/session_checker.rs rename to crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/session_utils/session_checker.rs index a1961a078d..2e0c0fa98d 100644 --- a/crates/service/web/storyteller_web/src/http_server/session/session_checker.rs +++ b/crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/session_utils/session_checker.rs @@ -14,9 +14,9 @@ use redis_common::redis_cache_keys::RedisCacheKeys; use sqlx::pool::PoolConnection; use sqlx::{Executor, MySql, MySqlConnection, MySqlPool}; -use crate::http_server::session::lookup::user_session_extended::{UserSessionExtended, UserSessionPreferences, UserSessionPremiumPlanInfo, UserSessionRoleAndPermissions, UserSessionSubscriptionPlan, UserSessionUserDetails}; -use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; -use crate::http_server::session::session_checker_error::SessionCheckerError; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_extended::{UserSessionExtended, UserSessionPreferences, UserSessionPremiumPlanInfo, UserSessionRoleAndPermissions, UserSessionSubscriptionPlan, UserSessionUserDetails}; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_feature_flags::UserSessionFeatureFlags; +use crate::http_server::user_lookup::user_session::session_utils::session_checker_error::SessionCheckerError; #[derive(Clone)] pub struct SessionChecker { diff --git a/crates/service/web/storyteller_web/src/http_server/session/session_checker_error.rs b/crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/session_utils/session_checker_error.rs similarity index 100% rename from crates/service/web/storyteller_web/src/http_server/session/session_checker_error.rs rename to crates/service/web/storyteller_web/src/http_server/user_lookup/user_session/session_utils/session_checker_error.rs diff --git a/crates/service/web/storyteller_web/src/startup/build_dependencies.rs b/crates/service/web/storyteller_web/src/startup/build_dependencies.rs index a2ed3b4a4a..4f35e1545c 100644 --- a/crates/service/web/storyteller_web/src/startup/build_dependencies.rs +++ b/crates/service/web/storyteller_web/src/startup/build_dependencies.rs @@ -26,7 +26,7 @@ use url_config::third_party_url_redirector::ThirdPartyUrlRedirector; use crate::configs::app_startup::redis_rate_limiters::configure_redis_rate_limiters; use crate::configs::connect_to_database::connect_to_database; use crate::configs::static_api_tokens::StaticApiTokenSet; -use crate::http_server::session::session_checker::SessionChecker; +use crate::http_server::user_lookup::user_session::session_utils::session_checker::SessionChecker; use crate::http_server::web_utils::scoped_temp_dir_creator::ScopedTempDirCreator; use crate::startup::setup_pager::build_pager; use crate::startup::setup_bans::{ diff --git a/crates/service/web/storyteller_web/src/state/server_state.rs b/crates/service/web/storyteller_web/src/state/server_state.rs index ac1ff64572..64bccf990a 100644 --- a/crates/service/web/storyteller_web/src/state/server_state.rs +++ b/crates/service/web/storyteller_web/src/state/server_state.rs @@ -6,7 +6,7 @@ use crate::http_server::endpoints::media_files::list::list_featured_media_files_ use crate::http_server::endpoints::stats::result_transformer::CacheableQueueStats; use crate::http_server::endpoints::tts::list_tts_models::TtsModelRecordForResponse; use crate::http_server::endpoints::voice_conversion::list_voice_conversion_models_handler::VoiceConversionModel; -use crate::http_server::session::session_checker::SessionChecker; +use crate::http_server::user_lookup::user_session::session_utils::session_checker::SessionChecker; use crate::http_server::web_utils::redis_rate_limiter::RedisRateLimiter; use crate::http_server::web_utils::scoped_temp_dir_creator::ScopedTempDirCreator; use crate::startup::setup_inference_providers::InferenceProviders; diff --git a/crates/service/web/storyteller_web/src/util/allowed_explore_media_access.rs b/crates/service/web/storyteller_web/src/util/allowed_explore_media_access.rs index 2deba14b88..0f86b68b1a 100644 --- a/crates/service/web/storyteller_web/src/util/allowed_explore_media_access.rs +++ b/crates/service/web/storyteller_web/src/util/allowed_explore_media_access.rs @@ -1,7 +1,7 @@ use enums::by_table::users::user_feature_flag::UserFeatureFlag; use mysql_queries::queries::users::user_sessions::get_user_session_by_token::SessionUserRecord; -use crate::http_server::session::lookup::user_session_extended::UserSessionExtended; -use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_extended::UserSessionExtended; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_feature_flags::UserSessionFeatureFlags; /// Check whether we should allow the request access to explore media. pub fn allowed_explore_media_access(maybe_session: Option) -> bool { diff --git a/crates/service/web/storyteller_web/src/util/allowed_studio_access.rs b/crates/service/web/storyteller_web/src/util/allowed_studio_access.rs index 475e2a0b67..2f9b19b482 100644 --- a/crates/service/web/storyteller_web/src/util/allowed_studio_access.rs +++ b/crates/service/web/storyteller_web/src/util/allowed_studio_access.rs @@ -1,7 +1,7 @@ use enums::by_table::users::user_feature_flag::UserFeatureFlag; use mysql_queries::queries::users::user_sessions::get_user_session_by_token::SessionUserRecord; -use crate::http_server::session::lookup::user_session_extended::UserSessionExtended; -use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_extended::UserSessionExtended; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_feature_flags::UserSessionFeatureFlags; use crate::state::server_state::StaticFeatureFlags; diff --git a/crates/service/web/storyteller_web/src/util/allowed_video_style_transfer_access.rs b/crates/service/web/storyteller_web/src/util/allowed_video_style_transfer_access.rs index c64f78d113..b7a82653ac 100644 --- a/crates/service/web/storyteller_web/src/util/allowed_video_style_transfer_access.rs +++ b/crates/service/web/storyteller_web/src/util/allowed_video_style_transfer_access.rs @@ -1,7 +1,7 @@ use enums::by_table::users::user_feature_flag::UserFeatureFlag; use mysql_queries::queries::users::user_sessions::get_user_session_by_token::SessionUserRecord; -use crate::http_server::session::lookup::user_session_extended::UserSessionExtended; -use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_extended::UserSessionExtended; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_feature_flags::UserSessionFeatureFlags; use crate::state::server_state::StaticFeatureFlags; diff --git a/crates/service/web/storyteller_web/src/util/enroll_in_studio.rs b/crates/service/web/storyteller_web/src/util/enroll_in_studio.rs index 31f51806a7..3bf422ec42 100644 --- a/crates/service/web/storyteller_web/src/util/enroll_in_studio.rs +++ b/crates/service/web/storyteller_web/src/util/enroll_in_studio.rs @@ -7,7 +7,7 @@ use mysql_queries::queries::users::user::update::set_can_access_studio_transacti use mysql_queries::queries::users::user::update::set_user_feature_flags_transactional::{set_user_feature_flags_transactional, SetUserFeatureFlagTransactionalArgs}; use tokens::tokens::users::UserToken; -use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; +use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_feature_flags::UserSessionFeatureFlags; pub async fn enroll_in_studio( user_token: &UserToken, From 7a0bfa35e622156d3df58aed8100717d68993e2b Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Tue, 16 Jun 2026 02:41:36 -0400 Subject: [PATCH 13/53] fixes for frontend build failures (#1658) --- .../lightbox-modal/src/lib/lightbox-modal.tsx | 1 - .../src/lib/model-selectors-for-pages.tsx | 1 - .../src/lib/provider-selector.tsx | 18 ++++----- .../libs/components/pagedraw/src/lib/Node.ts | 4 -- .../components/pagedraw/src/lib/PageDraw.tsx | 10 ++--- .../pagedraw/src/lib/PaintSurface.tsx | 2 - .../lib/PromptEditor/ImageStyleSelector.tsx | 4 -- .../src/lib/components/ui/SideToolbar.tsx | 1 - .../src/lib/hooks/useGlobalMouseUp.ts | 1 - .../ControlPanelSceneObject.tsx | 2 +- .../src/lib/comps/Outliner/Outliner.tsx | 14 +++---- .../contexts/EngineContext/EngineProvider.tsx | 2 +- .../pagescene/src/lib/engine/helper.ts | 38 +++++++++++-------- .../src/lib/engine/keybinds_controls.ts | 9 +++-- .../pagescene/src/lib/engine/scene.ts | 9 +++-- .../src/lib/engine/scene_manager_api.ts | 1 + .../pagescene/src/lib/hooks/useFreeCam.ts | 4 +- .../src/lib/hooks/useViewportKeyboard.ts | 2 +- .../src/lib/hooks/useViewportPointer.ts | 2 +- .../src/lib/hooks/useViewportSize.ts | 2 +- .../lib/proxy/storyteller_proxy_3d_object.ts | 4 ++ .../src/lib/cost-breakdown-modal.tsx | 1 - .../promptbox/src/lib/PromptBox3D.tsx | 2 +- .../promptbox/src/lib/PromptBoxEdit.tsx | 5 +-- .../src/lib/PromptBoxErrorBoundary.tsx | 6 +-- .../promptbox/src/lib/PromptBoxImage.tsx | 9 +---- .../promptbox/src/lib/PromptBoxVideo.tsx | 2 +- .../src/lib/UploadModal3D/UploadFiles3D.tsx | 13 ++++--- .../lib/UploadModalImage/UploadFilesImage.tsx | 13 ++++--- .../lib/UploadModalSplat/UploadFilesSplat.tsx | 13 ++++--- frontend/libs/state/subscription/src/index.ts | 6 +-- 31 files changed, 98 insertions(+), 103 deletions(-) diff --git a/frontend/libs/components/lightbox-modal/src/lib/lightbox-modal.tsx b/frontend/libs/components/lightbox-modal/src/lib/lightbox-modal.tsx index 5cb66f9d6b..7753c7f921 100644 --- a/frontend/libs/components/lightbox-modal/src/lib/lightbox-modal.tsx +++ b/frontend/libs/components/lightbox-modal/src/lib/lightbox-modal.tsx @@ -17,7 +17,6 @@ import { faVideo, faWandMagicSparkles, faArrowRotateRight, - faWaveformLines, } from "@fortawesome/pro-solid-svg-icons"; import { MediaFileDelete } from "@storyteller/tauri-api"; import { LoadingSpinner } from "@storyteller/ui-loading-spinner"; diff --git a/frontend/libs/components/model-selector/src/lib/model-selectors-for-pages.tsx b/frontend/libs/components/model-selector/src/lib/model-selectors-for-pages.tsx index c8d382b50d..249bfccf34 100644 --- a/frontend/libs/components/model-selector/src/lib/model-selectors-for-pages.tsx +++ b/frontend/libs/components/model-selector/src/lib/model-selectors-for-pages.tsx @@ -6,7 +6,6 @@ import { Model, VIDEO_MODELS, IMAGE_MODELS, - IMAGE_MODELS_BY_ID, SPLAT_MODELS, OBJECT_3D_MODELS, } from "@storyteller/model-list"; diff --git a/frontend/libs/components/model-selector/src/lib/provider-selector.tsx b/frontend/libs/components/model-selector/src/lib/provider-selector.tsx index 44f5e68827..ad9f3fad89 100644 --- a/frontend/libs/components/model-selector/src/lib/provider-selector.tsx +++ b/frontend/libs/components/model-selector/src/lib/provider-selector.tsx @@ -6,7 +6,7 @@ import { useEffect, useMemo } from "react"; import { PopoverMenu, type PopoverItem } from "@storyteller/ui-popover"; import { ModelPage } from "./model-pages"; import { Model } from "@storyteller/model-list"; -import { Provider } from "@storyteller/tauri-api"; +import { GenerationProvider } from "@storyteller/api-enums"; import { getProviderDisplayName, getProviderIcon } from "./provider-icons"; import { useClassyModelSelectorStore, @@ -16,17 +16,17 @@ import { interface ProviderSelectorProps { page: ModelPage; model: Model | undefined; - providersByModel?: Partial>; + providersByModel?: Partial>; panelTitle?: string; buttonClassName?: string; panelClassName?: string; triggerLabel?: string; } -const DEFAULT_PROVIDER_OPTIONS: Provider[] = [ - Provider.ArtCraft, - Provider.Fal, - Provider.Sora, +const DEFAULT_PROVIDER_OPTIONS: GenerationProvider[] = [ + GenerationProvider.Artcraft, + GenerationProvider.Fal, + GenerationProvider.Sora, ]; export function ProviderSelector({ @@ -38,7 +38,7 @@ export function ProviderSelector({ const { setSelectedProvider } = useClassyModelSelectorStore(); const modelId = model?.id; - const allowedProviders: Provider[] = useMemo(() => { + const allowedProviders: GenerationProvider[] = useMemo(() => { if (!modelId) return DEFAULT_PROVIDER_OPTIONS; return providersByModel?.[modelId] ?? DEFAULT_PROVIDER_OPTIONS; }, [providersByModel, modelId]); @@ -74,8 +74,8 @@ export function ProviderSelector({ const handleSelect = (item: PopoverItem) => { if (!modelId) return; - const prov: Provider | undefined = (item as any).provider as - | Provider + const prov: GenerationProvider | undefined = (item as any).provider as + | GenerationProvider | undefined; if (!prov) return; setSelectedProvider(page, modelId, prov); diff --git a/frontend/libs/components/pagedraw/src/lib/Node.ts b/frontend/libs/components/pagedraw/src/lib/Node.ts index ef76bd33e8..2435b466aa 100644 --- a/frontend/libs/components/pagedraw/src/lib/Node.ts +++ b/frontend/libs/components/pagedraw/src/lib/Node.ts @@ -173,7 +173,3 @@ export class Node { } } } - -function generateId(): string { - return `node-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; -} diff --git a/frontend/libs/components/pagedraw/src/lib/PageDraw.tsx b/frontend/libs/components/pagedraw/src/lib/PageDraw.tsx index 58f86b6f50..dab2741090 100644 --- a/frontend/libs/components/pagedraw/src/lib/PageDraw.tsx +++ b/frontend/libs/components/pagedraw/src/lib/PageDraw.tsx @@ -198,7 +198,7 @@ function DragScrubButton({ interface Edit3DScrubControlsProps { nodeId: string; stageRef: { current: Konva.Stage }; - overlayHandle: React.RefObject; + overlayHandle: React.RefObject; } const Edit3DScrubControls = memo(function Edit3DScrubControls({ @@ -351,7 +351,6 @@ const PageDraw = ({ adapter }: PageDrawProps) => { historyImageBundles, pendingGenerations, getAspectRatioDimensions, - finishRemoveBackground, createImageFromUrl, createImageFromFile, createImageFrom3DModel, @@ -361,7 +360,6 @@ const PageDraw = ({ adapter }: PageDrawProps) => { removeHistoryImage, addHistoryImageBundle, addPendingGeneration, - resolvePendingGeneration, removePendingGeneration, clearPendingGenerations, setAspectRatioType, @@ -391,8 +389,6 @@ const PageDraw = ({ adapter }: PageDrawProps) => { (state) => state.setGenerationCount, ); const useSystemPrompt = promptStoreProvider((state) => state.useSystemPrompt); - const referenceImages = promptStoreProvider((state) => state.referenceImages); - const prompt = promptStoreProvider((state) => state.prompt); const baseImageKonvaRef = useRef({} as Konva.Image); const baseImageUrl = baseImageInfo?.url; @@ -524,13 +520,13 @@ const PageDraw = ({ adapter }: PageDrawProps) => { window.addEventListener( "gallery-2d-drop", - handleGallery2DDrop as EventListener, + handleGallery2DDrop as unknown as EventListener, ); return () => { window.removeEventListener( "gallery-2d-drop", - handleGallery2DDrop as EventListener, + handleGallery2DDrop as unknown as EventListener, ); }; }, [createImageFromUrl, createImageFrom3DModel]); diff --git a/frontend/libs/components/pagedraw/src/lib/PaintSurface.tsx b/frontend/libs/components/pagedraw/src/lib/PaintSurface.tsx index 41dfdf85ea..e68d6460d8 100644 --- a/frontend/libs/components/pagedraw/src/lib/PaintSurface.tsx +++ b/frontend/libs/components/pagedraw/src/lib/PaintSurface.tsx @@ -15,12 +15,10 @@ import { Line, Image, Transformer, - Group, } from "react-konva"; import Konva from "konva"; import { LineNode, SceneState, useSceneStore } from "./stores/SceneState"; import { Node } from "./Node"; -import { useStageSnapshot } from "./hooks/useUpdateSnapshot"; import "./pagedraw.css"; import SplitPane from "./components/ui/SplitPane"; import { useRightPanelLayoutManagement } from "./hooks/useRightPanelLayoutManagement"; diff --git a/frontend/libs/components/pagedraw/src/lib/PromptEditor/ImageStyleSelector.tsx b/frontend/libs/components/pagedraw/src/lib/PromptEditor/ImageStyleSelector.tsx index efbc4e35d1..63a8133af6 100644 --- a/frontend/libs/components/pagedraw/src/lib/PromptEditor/ImageStyleSelector.tsx +++ b/frontend/libs/components/pagedraw/src/lib/PromptEditor/ImageStyleSelector.tsx @@ -4,10 +4,6 @@ import { ImageStyleSelectorProps } from './types'; const ImageStyleSelector: React.FC = ({ onImageSelect }) => { const fileInputRef = useRef(null); - const handleClick = () => { - fileInputRef.current?.click(); - }; - const handleFileChange = (e: React.ChangeEvent) => { const files = e.target.files; if (files && files.length > 0) { diff --git a/frontend/libs/components/pagedraw/src/lib/components/ui/SideToolbar.tsx b/frontend/libs/components/pagedraw/src/lib/components/ui/SideToolbar.tsx index 418c0d6ce1..da59324a0f 100644 --- a/frontend/libs/components/pagedraw/src/lib/components/ui/SideToolbar.tsx +++ b/frontend/libs/components/pagedraw/src/lib/components/ui/SideToolbar.tsx @@ -2,7 +2,6 @@ import React, { useState, useRef, useEffect } from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faMousePointer, - faTrash, faEyeDropper, faImage, faUndo, diff --git a/frontend/libs/components/pagedraw/src/lib/hooks/useGlobalMouseUp.ts b/frontend/libs/components/pagedraw/src/lib/hooks/useGlobalMouseUp.ts index 17ecdac735..c80810e42b 100644 --- a/frontend/libs/components/pagedraw/src/lib/hooks/useGlobalMouseUp.ts +++ b/frontend/libs/components/pagedraw/src/lib/hooks/useGlobalMouseUp.ts @@ -1,4 +1,3 @@ -import { Vector2d } from "konva/lib/types"; import { useEffect } from "react"; import { DragState } from "../types"; // This piece of code ensures that when you are selecting or dragging and go off the stage, diff --git a/frontend/libs/components/pagescene/src/lib/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx b/frontend/libs/components/pagescene/src/lib/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx index a0e0683c18..cee00533f2 100644 --- a/frontend/libs/components/pagescene/src/lib/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx +++ b/frontend/libs/components/pagescene/src/lib/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx @@ -210,7 +210,7 @@ export const ControlPanelSceneObject = () => { // document mouseup/keydown listeners can interact poorly with the // freecam pointer-capture and canvas keydown handlers). useEffect(() => { - if (!isShowing) return; + if (!isShowing) return undefined; const flush = () => { transformSessionRef.current?.commit(); transformSessionRef.current = null; diff --git a/frontend/libs/components/pagescene/src/lib/comps/Outliner/Outliner.tsx b/frontend/libs/components/pagescene/src/lib/comps/Outliner/Outliner.tsx index 5b99dccc20..a415fd5a82 100644 --- a/frontend/libs/components/pagescene/src/lib/comps/Outliner/Outliner.tsx +++ b/frontend/libs/components/pagescene/src/lib/comps/Outliner/Outliner.tsx @@ -151,14 +151,12 @@ export const Outliner = () => { } } - if (viewport.width < 2000) { - if (camAspect === CameraAspectRatio.VERTICAL_9_16) { - return `${editorHeight * 0.7 - 10}px`; - } else if (camAspect === CameraAspectRatio.SQUARE_1_1) { - return `${editorHeight * 0.7}px`; - } else { - return `${editorHeight * 0.7}px`; - } + if (camAspect === CameraAspectRatio.VERTICAL_9_16) { + return `${editorHeight * 0.7 - 10}px`; + } else if (camAspect === CameraAspectRatio.SQUARE_1_1) { + return `${editorHeight * 0.7}px`; + } else { + return `${editorHeight * 0.7}px`; } }; diff --git a/frontend/libs/components/pagescene/src/lib/contexts/EngineContext/EngineProvider.tsx b/frontend/libs/components/pagescene/src/lib/contexts/EngineContext/EngineProvider.tsx index 371cec13ef..c2a3cf718c 100644 --- a/frontend/libs/components/pagescene/src/lib/contexts/EngineContext/EngineProvider.tsx +++ b/frontend/libs/components/pagescene/src/lib/contexts/EngineContext/EngineProvider.tsx @@ -59,7 +59,7 @@ export const EngineProvider = ({ // available; callback refs in SceneContainer / EditorCanvas / // CameraViewCanvas drive this by setting their nodes (and clearing // them to null on unmount). - if (!sceneContainer || !editorCanvas || !camViewCanvas) return; + if (!sceneContainer || !editorCanvas || !camViewCanvas) return undefined; const newEditor = new Editor(adapterRef.current); newEditor.initialize({ diff --git a/frontend/libs/components/pagescene/src/lib/engine/helper.ts b/frontend/libs/components/pagescene/src/lib/engine/helper.ts index 66b02fc4fd..b90ef1d826 100644 --- a/frontend/libs/components/pagescene/src/lib/engine/helper.ts +++ b/frontend/libs/components/pagescene/src/lib/engine/helper.ts @@ -153,29 +153,35 @@ function removeObject3D(object3D) { obj.traverse(child => { (child as THREE.Mesh)?.geometry?.dispose() - if (Array.isArray((child as THREE.Mesh).texture)) { - (child as THREE.Mesh).texture.forEach(mat => mat.dispose()); - } else if ((child as THREE.Mesh).texture) { - (child as THREE.Mesh).texture.dispose(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const childTexture = (child as any).texture; + if (Array.isArray(childTexture)) { + childTexture.forEach(mat => mat.dispose()); + } else if (childTexture) { + childTexture.dispose(); } - if (Array.isArray((child as THREE.Mesh).material)) { - (child as THREE.Mesh).material.forEach(mat => mat.dispose()); - } else if ((child as THREE.Mesh).material) { - (child as THREE.Mesh).material.dispose(); + const childMaterial = (child as THREE.Mesh).material; + if (Array.isArray(childMaterial)) { + childMaterial.forEach(mat => mat.dispose()); + } else if (childMaterial) { + childMaterial.dispose(); } }) - if (Array.isArray((obj as THREE.Mesh).texture)) { - (obj as THREE.Mesh).texture.forEach(mat => mat.dispose()); - } else if ((obj as THREE.Mesh).texture) { - (obj as THREE.Mesh).texture.dispose(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const objTexture = (obj as any).texture; + if (Array.isArray(objTexture)) { + objTexture.forEach(mat => mat.dispose()); + } else if (objTexture) { + objTexture.dispose(); } - if (Array.isArray((obj as THREE.Mesh).material)) { - (obj as THREE.Mesh).material.forEach(mat => mat.dispose()); - } else if ((obj as THREE.Mesh).material) { - (obj as THREE.Mesh).material.dispose(); + const objMaterial = (obj as THREE.Mesh).material; + if (Array.isArray(objMaterial)) { + objMaterial.forEach(mat => mat.dispose()); + } else if (objMaterial) { + objMaterial.dispose(); } if ((obj as THREE.Mesh).geometry) { diff --git a/frontend/libs/components/pagescene/src/lib/engine/keybinds_controls.ts b/frontend/libs/components/pagescene/src/lib/engine/keybinds_controls.ts index 89b2a080d0..813b9ae585 100644 --- a/frontend/libs/components/pagescene/src/lib/engine/keybinds_controls.ts +++ b/frontend/libs/components/pagescene/src/lib/engine/keybinds_controls.ts @@ -399,7 +399,8 @@ export class MouseControls { event.preventDefault(); this.isProcessing = true; try { - await this.sceneManager?.undo(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await (this.sceneManager as any)?.undo(); } finally { this.isProcessing = false; } @@ -408,7 +409,8 @@ export class MouseControls { event.preventDefault(); this.isProcessing = true; try { - await this.sceneManager?.redo(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await (this.sceneManager as any)?.redo(); } finally { this.isProcessing = false; } @@ -417,7 +419,8 @@ export class MouseControls { event.preventDefault(); this.isProcessing = true; try { - await this.sceneManager?.redo(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await (this.sceneManager as any)?.redo(); } finally { this.isProcessing = false; } diff --git a/frontend/libs/components/pagescene/src/lib/engine/scene.ts b/frontend/libs/components/pagescene/src/lib/engine/scene.ts index fa65060e80..6be7a80738 100644 --- a/frontend/libs/components/pagescene/src/lib/engine/scene.ts +++ b/frontend/libs/components/pagescene/src/lib/engine/scene.ts @@ -372,7 +372,7 @@ class Scene { ); if (obj_keyframe_uuid === keyframe_uuid) { keyframe_point = object; - return object; + return; } } }); @@ -611,7 +611,7 @@ class Scene { async loadObjectFromUrl( url: string, position: THREE.Vector3 = new THREE.Vector3(-0.5, 1.5, 0), - ): Promise { + ): Promise { console.log("loadObjectFromUrl!!!!!!!!", url); if ( url.includes(".png") || @@ -628,6 +628,7 @@ class Scene { } return obj; } + return undefined; } async loadObject( @@ -910,8 +911,8 @@ class Scene { media_url: string, onComplete: () => void, signal?: AbortSignal, - ) { - return new Promise(async (resolve, reject) => { + ): Promise { + return new Promise(async (resolve, reject) => { let buffer; try { // Host-supplied CORS-bypassed binary fetch (FetchProxy under diff --git a/frontend/libs/components/pagescene/src/lib/engine/scene_manager_api.ts b/frontend/libs/components/pagescene/src/lib/engine/scene_manager_api.ts index ec58825976..dfec805b28 100644 --- a/frontend/libs/components/pagescene/src/lib/engine/scene_manager_api.ts +++ b/frontend/libs/components/pagescene/src/lib/engine/scene_manager_api.ts @@ -92,6 +92,7 @@ export class SceneManager implements SceneManagerAPI { } else { return this.scene.instantiate(name, position); } + return undefined; } updateSkybox(media_id: string) { diff --git a/frontend/libs/components/pagescene/src/lib/hooks/useFreeCam.ts b/frontend/libs/components/pagescene/src/lib/hooks/useFreeCam.ts index 2976c4ba39..228500b857 100644 --- a/frontend/libs/components/pagescene/src/lib/hooks/useFreeCam.ts +++ b/frontend/libs/components/pagescene/src/lib/hooks/useFreeCam.ts @@ -89,7 +89,7 @@ export const useFreeCam = ( // Hand the state to the editor so its render loop can integrate it. useEffect(() => { - if (!editor) return; + if (!editor) return undefined; editor.cameraController.setFreeCamState(stateRef.current); return () => editor.cameraController.setFreeCamState(null); }, [editor]); @@ -98,7 +98,7 @@ export const useFreeCam = ( // inside the viewport); keys on document so the user doesn't need // to click canvas first for WASD to work. useEffect(() => { - if (!canvas || !editor) return; + if (!canvas || !editor) return undefined; const state = stateRef.current; state.enabled = true; diff --git a/frontend/libs/components/pagescene/src/lib/hooks/useViewportKeyboard.ts b/frontend/libs/components/pagescene/src/lib/hooks/useViewportKeyboard.ts index c598169efa..348b2dc6e8 100644 --- a/frontend/libs/components/pagescene/src/lib/hooks/useViewportKeyboard.ts +++ b/frontend/libs/components/pagescene/src/lib/hooks/useViewportKeyboard.ts @@ -44,7 +44,7 @@ export const useViewportKeyboard = (editor: Editor | null) => { const bindings = useMemo(() => buildKeymap(), []); useEffect(() => { - if (!editor) return; + if (!editor) return undefined; const onKeyDown = (event: KeyboardEvent) => { if (isEventFromEditableElement(event)) return; diff --git a/frontend/libs/components/pagescene/src/lib/hooks/useViewportPointer.ts b/frontend/libs/components/pagescene/src/lib/hooks/useViewportPointer.ts index 3c522a22ab..45a8b0840e 100644 --- a/frontend/libs/components/pagescene/src/lib/hooks/useViewportPointer.ts +++ b/frontend/libs/components/pagescene/src/lib/hooks/useViewportPointer.ts @@ -25,7 +25,7 @@ export const useViewportPointer = ( ); useEffect(() => { - if (!canvas || !editor) return; + if (!canvas || !editor) return undefined; const onPointerMove = (e: PointerEvent) => { if (!editor.mouse) return; diff --git a/frontend/libs/components/pagescene/src/lib/hooks/useViewportSize.ts b/frontend/libs/components/pagescene/src/lib/hooks/useViewportSize.ts index 01ec3dada0..210ab78cb9 100644 --- a/frontend/libs/components/pagescene/src/lib/hooks/useViewportSize.ts +++ b/frontend/libs/components/pagescene/src/lib/hooks/useViewportSize.ts @@ -18,7 +18,7 @@ export const useViewportSize = (): { width: number; height: number } => { ); useEffect(() => { - if (typeof window === "undefined") return; + if (typeof window === "undefined") return undefined; const onResize = () => { setSize( adapterGet?.() ?? { diff --git a/frontend/libs/components/pagescene/src/lib/proxy/storyteller_proxy_3d_object.ts b/frontend/libs/components/pagescene/src/lib/proxy/storyteller_proxy_3d_object.ts index ed076f8acd..9565618b96 100644 --- a/frontend/libs/components/pagescene/src/lib/proxy/storyteller_proxy_3d_object.ts +++ b/frontend/libs/components/pagescene/src/lib/proxy/storyteller_proxy_3d_object.ts @@ -22,6 +22,10 @@ export interface ObjectJSON { object_uuid: string; object_user_data_name: string; media_file_token: string; + // Legacy top-level field. Not written by toJSON() (the value is + // carried inside user_data.media_file_type); kept optional so reads + // that reference it directly type-check without changing behavior. + media_file_type?: MediaFileType; color: string; metalness: number; shininess: number; diff --git a/frontend/libs/components/pricing-modal/src/lib/cost-breakdown-modal.tsx b/frontend/libs/components/pricing-modal/src/lib/cost-breakdown-modal.tsx index 0ed6eb4058..66be842be9 100644 --- a/frontend/libs/components/pricing-modal/src/lib/cost-breakdown-modal.tsx +++ b/frontend/libs/components/pricing-modal/src/lib/cost-breakdown-modal.tsx @@ -96,7 +96,6 @@ export function CostBreakdownModal({ activeTabId }: CostBreakdownModalProps) { const { currency, setCurrency, - currencyOption, formatPrice, currencyOptions, } = useCurrency(); diff --git a/frontend/libs/components/promptbox/src/lib/PromptBox3D.tsx b/frontend/libs/components/promptbox/src/lib/PromptBox3D.tsx index eac8ec2e42..d21930a0d8 100644 --- a/frontend/libs/components/promptbox/src/lib/PromptBox3D.tsx +++ b/frontend/libs/components/promptbox/src/lib/PromptBox3D.tsx @@ -547,7 +547,7 @@ export const PromptBox3D = ({ }; const getCurrentAspectRatio = (): CommonAspectRatio => { - switch (cameraAspectRatio.value) { + switch (cameraAspectRatio) { case CameraAspectRatio.HORIZONTAL_3_2: return CommonAspectRatio.Wide; case CameraAspectRatio.VERTICAL_2_3: diff --git a/frontend/libs/components/promptbox/src/lib/PromptBoxEdit.tsx b/frontend/libs/components/promptbox/src/lib/PromptBoxEdit.tsx index 1c29ad488c..3304402d69 100644 --- a/frontend/libs/components/promptbox/src/lib/PromptBoxEdit.tsx +++ b/frontend/libs/components/promptbox/src/lib/PromptBoxEdit.tsx @@ -5,8 +5,6 @@ import { } from "@fortawesome/pro-regular-svg-icons"; import { faEdit, - faMessageCheck, - faMessageXmark, faMousePointer, faFrame, faPen, @@ -19,7 +17,7 @@ import { faChevronUp, } from "@fortawesome/pro-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Button, ToggleButton, GenerateButton } from "@storyteller/ui-button"; +import { Button, GenerateButton } from "@storyteller/ui-button"; import { ButtonIconSelect } from "@storyteller/ui-button-icon-select"; import { PopoverMenu, PopoverItem } from "@storyteller/ui-popover"; import { Tooltip } from "@storyteller/ui-tooltip"; @@ -83,7 +81,6 @@ export const PromptBoxEdit = ({ credits, }: PromptBoxEditProps) => { const [prompt, setPrompt] = useState(""); - const [useSystemPrompt, setUseSystemPrompt] = useState(true); const [isFocused, setIsFocused] = useState(false); const [isExpanded, setIsExpanded] = useState(false); diff --git a/frontend/libs/components/promptbox/src/lib/PromptBoxErrorBoundary.tsx b/frontend/libs/components/promptbox/src/lib/PromptBoxErrorBoundary.tsx index 0cb49143f5..91f741a611 100644 --- a/frontend/libs/components/promptbox/src/lib/PromptBoxErrorBoundary.tsx +++ b/frontend/libs/components/promptbox/src/lib/PromptBoxErrorBoundary.tsx @@ -11,17 +11,17 @@ interface State { } export class PromptBoxErrorBoundary extends Component { - state: State = { hasError: false, resetKey: 0 }; + override state: State = { hasError: false, resetKey: 0 }; static getDerivedStateFromError(): Partial { return { hasError: true }; } - componentDidCatch(error: Error, info: ErrorInfo) { + override componentDidCatch(error: Error, info: ErrorInfo) { console.error("PromptBox crashed:", error, info.componentStack); } - render() { + override render() { if (this.state.hasError) { return (
diff --git a/frontend/libs/components/promptbox/src/lib/PromptBoxImage.tsx b/frontend/libs/components/promptbox/src/lib/PromptBoxImage.tsx index 7cad964ec2..c1ed36822f 100644 --- a/frontend/libs/components/promptbox/src/lib/PromptBoxImage.tsx +++ b/frontend/libs/components/promptbox/src/lib/PromptBoxImage.tsx @@ -4,18 +4,16 @@ import { JobContextType, UploaderState } from "@storyteller/common"; import { toast } from "@storyteller/ui-toaster"; import { PopoverMenu, PopoverItem } from "@storyteller/ui-popover"; import { Tooltip } from "@storyteller/ui-tooltip"; -import { Button, ToggleButton, GenerateButton } from "@storyteller/ui-button"; +import { Button, GenerateButton } from "@storyteller/ui-button"; import { Modal } from "@storyteller/ui-modal"; import { GenerateImage, GenerateImageRequest } from "@storyteller/tauri-api"; import { - faMessageXmark, - faMessageCheck, faExpand, faChevronDown, faChevronUp, } from "@fortawesome/pro-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { CommonAspectRatio, ImageModel } from "@storyteller/model-list"; +import { ImageModel } from "@storyteller/model-list"; import { usePromptImageStore, RefImage, @@ -30,7 +28,6 @@ import { AspectRatioIcon } from "./common/AspectRatioIcon"; import { GenerationCountPicker } from "./common/GenerationCountPicker"; import { ResolutionPicker } from "./common/ResolutionPicker"; import { QualityPicker } from "./common/QualityPicker"; -import { CommonResolution } from "@storyteller/model-list"; interface PromptBoxImageProps { useJobContext: () => JobContextType; @@ -96,8 +93,6 @@ export const PromptBoxImage = ({ const prompt = usePromptImageStore((s) => s.prompt); const setPrompt = usePromptImageStore((s) => s.setPrompt); - const useSystemPrompt = usePromptImageStore((s) => s.useSystemPrompt); - const setUseSystemPrompt = usePromptImageStore((s) => s.setUseSystemPrompt); const aspectRatio = usePromptImageStore((s) => s.aspectRatio); const setAspectRatio = usePromptImageStore((s) => s.setAspectRatio); const resolution = usePromptImageStore((s) => s.resolution); diff --git a/frontend/libs/components/promptbox/src/lib/PromptBoxVideo.tsx b/frontend/libs/components/promptbox/src/lib/PromptBoxVideo.tsx index 8e6629661d..da9c14f8af 100644 --- a/frontend/libs/components/promptbox/src/lib/PromptBoxVideo.tsx +++ b/frontend/libs/components/promptbox/src/lib/PromptBoxVideo.tsx @@ -274,7 +274,7 @@ export const PromptBoxVideo = ({ aspectRatioOptions = buildAspectRatioOptions(DEFAULT_RESOLUTIONS); } - const [aspectRatioList, setAspectRatioList] = + const [, setAspectRatioList] = useState(aspectRatioOptions); const textareaRef = useRef(null); diff --git a/frontend/libs/components/upload-modal/src/lib/UploadModal3D/UploadFiles3D.tsx b/frontend/libs/components/upload-modal/src/lib/UploadModal3D/UploadFiles3D.tsx index 7b81c1e92e..a9e8f603b8 100644 --- a/frontend/libs/components/upload-modal/src/lib/UploadModal3D/UploadFiles3D.tsx +++ b/frontend/libs/components/upload-modal/src/lib/UploadModal3D/UploadFiles3D.tsx @@ -349,8 +349,8 @@ export const UploadFiles3D = ({ {selectionError &&
{selectionError}
} {isMulti ? ( - - + +