This commit is contained in:
Brandon Thomas committed 2026-04-14 01:40:49 -04:00
1 parent 588478da73
commit 5ade75222e
11 files changed
+387 -24

No files matched your search

Generated
+1
View File
@@ -11280,6 +11280,7 @@ dependencies = [
"mimetypes",
"mysql_queries",
"once_cell",
"opaque_cursors",
"pager",
"password",
"primitives",
@@ -0,0 +1,74 @@
# mysql_queries
SQLx-based MySQL queries for the storyteller-web backend and background jobs.
## Building
```
SQLX_OFFLINE=true cargo check -p mysql_queries
```
The `SQLX_OFFLINE=true` env var is **required** for compilation. This crate uses the sqlx
offline cache (`.sqlx/` directory at the workspace root) to validate queries at compile time
without a live database connection.
## Query Style
### Prefer `sqlx::query!` and `sqlx::query_as!` macros
Always use the compile-time checked `sqlx::query!` and `sqlx::query_as!` macros over the
runtime `sqlx::query()` and `sqlx::query_as()` functions. The macros validate SQL syntax,
column names, and parameter types against the offline cache at compile time.
```rust
// PREFERRED: compile-time checked
sqlx::query!(
r#"INSERT INTO my_table SET token = ?, name = ?"#,
token,
name,
)
.execute(args.mysql_executor)
.await?;
// AVOID: runtime only, no compile-time checking
sqlx::query(r#"INSERT INTO my_table SET token = ?, name = ?"#)
.bind(token)
.bind(name)
.execute(args.mysql_executor)
.await?;
```
Use runtime `sqlx::query()` only when:
- The query involves a table not yet in the offline cache and you cannot run `cargo sqlx prepare`
- The query is dynamically constructed (rare)
- SELECT queries for new tables where column type metadata isn't cached yet
### Offline Cache
When adding new queries for **existing tables**, the `.sqlx/` cache should already have the
schema metadata. For **new tables**, you may need to:
1. Run the migration against a real database
2. Run `cargo sqlx prepare` to regenerate the cache
3. Or manually create cache entries for simple INSERT/UPDATE queries (they only need parameter count)
### Error Types
New queries should return `Result<T, sqlx::Error>` instead of the legacy `AnyhowResult<T>`.
The `AnyhowResult` pattern is being phased out.
### Transaction Support
Use the executor-generic `Args` pattern for queries that need to work inside transactions:
```rust
pub struct MyQueryArgs<'a, 'c: 'a, E>
where E: 'a + Executor<'c, Database = MySql>
{
pub field: &'a str,
pub mysql_executor: E,
pub phantom: PhantomData<&'c E>,
}
```
This allows the same query function to accept both `&MySqlPool` and `&mut Transaction`.
@@ -0,0 +1,116 @@
use chrono::{DateTime, Utc};
use sqlx::{MySqlPool, Row};
use tokens::tokens::users::UserToken;
pub struct UserImpersonationRequestListItem {
pub id: u64,
pub impersonator_user_token: UserToken,
pub impersonator_username: String,
pub impersonator_display_name: String,
pub impersonated_user_token: UserToken,
pub impersonated_username: String,
pub impersonated_display_name: String,
pub is_redeemed: bool,
pub is_expired: bool,
pub expires_at: DateTime<Utc>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
pub struct ListUserImpersonationRequestsArgs<'a> {
pub user_token: &'a str,
pub maybe_cursor_id: Option<u64>,
pub limit: u32,
pub mysql_pool: &'a MySqlPool,
}
pub async fn list_user_impersonation_requests_for_user(
args: ListUserImpersonationRequestsArgs<'_>,
) -> Result<Vec<UserImpersonationRequestListItem>, sqlx::Error> {
let limit = args.limit as i64;
let rows = match args.maybe_cursor_id {
Some(cursor_id) => {
let cursor_id = cursor_id as i64;
sqlx::query(
r#"
SELECT
uir.id,
uir.impersonator_user_token,
impersonator.username as impersonator_username,
impersonator.display_name as impersonator_display_name,
uir.impersonated_user_token,
impersonated.username as impersonated_username,
impersonated.display_name as impersonated_display_name,
uir.is_redeemed,
(uir.expires_at < NOW()) as is_expired,
uir.expires_at,
uir.created_at,
uir.updated_at
FROM user_impersonation_requests uir
JOIN users impersonator ON impersonator.token = uir.impersonator_user_token
JOIN users impersonated ON impersonated.token = uir.impersonated_user_token
WHERE uir.impersonated_user_token = ?
AND uir.id < ?
ORDER BY uir.id DESC
LIMIT ?
"#,
)
.bind(args.user_token)
.bind(cursor_id)
.bind(limit)
.fetch_all(args.mysql_pool)
.await?
}
None => {
sqlx::query(
r#"
SELECT
uir.id,
uir.impersonator_user_token,
impersonator.username as impersonator_username,
impersonator.display_name as impersonator_display_name,
uir.impersonated_user_token,
impersonated.username as impersonated_username,
impersonated.display_name as impersonated_display_name,
uir.is_redeemed,
(uir.expires_at < NOW()) as is_expired,
uir.expires_at,
uir.created_at,
uir.updated_at
FROM user_impersonation_requests uir
JOIN users impersonator ON impersonator.token = uir.impersonator_user_token
JOIN users impersonated ON impersonated.token = uir.impersonated_user_token
WHERE uir.impersonated_user_token = ?
ORDER BY uir.id DESC
LIMIT ?
"#,
)
.bind(args.user_token)
.bind(limit)
.fetch_all(args.mysql_pool)
.await?
}
};
let items = rows.into_iter().map(|row| {
UserImpersonationRequestListItem {
id: row.get::<i64, _>("id") as u64,
impersonator_user_token: UserToken::new_from_str(row.get("impersonator_user_token")),
impersonator_username: row.get("impersonator_username"),
impersonator_display_name: row.get("impersonator_display_name"),
impersonated_user_token: UserToken::new_from_str(row.get("impersonated_user_token")),
impersonated_username: row.get("impersonated_username"),
impersonated_display_name: row.get("impersonated_display_name"),
is_redeemed: row.get("is_redeemed"),
is_expired: row.get::<i64, _>("is_expired") != 0,
expires_at: row.get("expires_at"),
created_at: row.get("created_at"),
updated_at: row.get("updated_at"),
}
}).collect();
Ok(items)
}
@@ -1,3 +1,4 @@
pub mod insert_user_impersonation_request;
pub mod list_user_impersonation_requests_for_user;
pub mod lookup_user_impersonation_request;
pub mod mark_impersonation_token_as_redeemed;
@@ -65,6 +65,7 @@ memory_caching = { path = "../../../lib/caching/memory_caching" }
migration = { path = "../../../schema/database/migration" }
mimetypes.workspace = true
mysql_queries.workspace = true
opaque_cursors.workspace = true
pager.workspace = true
password = { path = "../../../lib/password" }
rootly_client.workspace = true
@@ -86,6 +86,7 @@ use crate::http_server::endpoints::media_files::upsert_upload::write_error::Medi
use crate::http_server::endpoints::media_files::upsert_upload::write_scene_file::write_scene_file_media_file_handler::*;
use crate::http_server::endpoints::model_download::enqueue_gptsovits_model_download_handler::*;
use crate::http_server::endpoints::moderation::user_feature_flags::edit_user_feature_flags_handler::*;
use crate::http_server::endpoints::moderation::user_sessions::moderator_list_user_session_impersonation_requests_for_user_handler::*;
use crate::http_server::endpoints::moderation::user_sessions::moderator_user_session_impersonation_request_handler::*;
use crate::http_server::endpoints::prompts::get_prompt_handler::*;
use artcraft_api_defs::common::responses::job_details::JobDetailsLipsyncRequest;
@@ -508,6 +509,7 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux
crate::http_server::endpoints::moderation::wallets::moderator_add_banked_balance_to_wallet_handler::moderator_add_banked_balance_to_wallet_handler,
crate::http_server::endpoints::moderation::wallets::moderator_create_wallet_for_user_handler::moderator_create_wallet_for_user_handler,
crate::http_server::endpoints::moderation::wallets::moderator_get_wallet_handler::moderator_get_wallet_handler,
crate::http_server::endpoints::moderation::user_sessions::moderator_list_user_session_impersonation_requests_for_user_handler::moderator_list_user_session_impersonation_requests_for_user_handler,
crate::http_server::endpoints::moderation::user_sessions::moderator_user_session_impersonation_request_handler::moderator_user_session_impersonation_request_handler,
// Credits
crate::http_server::endpoints::credits::get_session_credits_handler::get_session_credits_handler,
@@ -887,6 +889,9 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux
ListFeaturedWeightsError,
ListFeaturedWeightsQueryParams,
ListFeaturedWeightsSuccessResponse,
ListImpersonationRequestsPathInfo,
ListImpersonationRequestsQueryParams,
ListUserImpersonationRequestsSuccessResponse,
ListMediaFilesByBatchPathInfo,
ListMediaFilesByBatchSuccessResponse,
ListMediaFilesForUserPathInfo,
@@ -995,6 +1000,7 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux
UploadEngineAssetMediaSuccessResponse,
UploadImageMediaFileForm,
UploadImageMediaFileSuccessResponse,
UserImpersonationRequestResponse,
UploadMediaSuccessResponse,
UploadNewEngineAssetFileForm,
UploadNewEngineAssetSuccessResponse,
@@ -1 +1,2 @@
pub mod moderator_list_user_session_impersonation_requests_for_user_handler;
pub mod moderator_user_session_impersonation_request_handler;
@@ -0,0 +1,152 @@
use std::sync::Arc;
use actix_web::web::{Json, Path, Query};
use actix_web::{web, HttpRequest};
use chrono::{DateTime, Utc};
use log::warn;
use utoipa::{IntoParams, ToSchema};
use mysql_queries::queries::user_impersonation_requests::list_user_impersonation_requests_for_user::{
list_user_impersonation_requests_for_user, ListUserImpersonationRequestsArgs,
};
use tokens::tokens::users::UserToken;
use crate::http_server::common_responses::advanced_common_web_error::AdvancedCommonWebError;
use crate::http_server::web_utils::user_session::require_moderator::{
require_moderator, UseDatabase,
};
use crate::state::server_state::ServerState;
const CURSOR_NAME: &str = "moduserimp";
const DEFAULT_LIMIT: u32 = 50;
const MAX_LIMIT: u32 = 1000;
// --- Request ---
#[derive(Deserialize, ToSchema)]
pub struct ListImpersonationRequestsPathInfo {
user_token: UserToken,
}
#[derive(Deserialize, ToSchema, IntoParams)]
pub struct ListImpersonationRequestsQueryParams {
pub cursor: Option<String>,
pub limit: Option<u32>,
}
// --- Response ---
#[derive(Serialize, ToSchema)]
pub struct ListUserImpersonationRequestsSuccessResponse {
pub success: bool,
pub impersonation_requests: Vec<UserImpersonationRequestResponse>,
pub maybe_cursor: Option<String>,
}
#[derive(Serialize, ToSchema)]
pub struct UserImpersonationRequestResponse {
pub impersonator_user_token: UserToken,
pub impersonator_username: String,
pub impersonator_display_name: String,
pub impersonated_user_token: UserToken,
pub impersonated_username: String,
pub impersonated_display_name: String,
pub is_redeemed: bool,
pub is_expired: bool,
pub expires_at: DateTime<Utc>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
// --- Handler ---
/// List impersonation requests for a given user. Moderators only.
#[utoipa::path(
get,
tag = "Moderation",
path = "/v1/moderation/user_sessions/impersonation_requests/{user_token}",
params(
ListImpersonationRequestsQueryParams,
),
responses(
(status = 200, description = "Success", body = ListUserImpersonationRequestsSuccessResponse),
(status = 401, description = "Unauthorized"),
(status = 500, description = "Server error"),
),
)]
pub async fn moderator_list_user_session_impersonation_requests_for_user_handler(
http_request: HttpRequest,
path: Path<ListImpersonationRequestsPathInfo>,
query: Query<ListImpersonationRequestsQueryParams>,
server_state: web::Data<Arc<ServerState>>,
) -> Result<Json<ListUserImpersonationRequestsSuccessResponse>, AdvancedCommonWebError> {
let _user_session = require_moderator(
&http_request,
&server_state,
UseDatabase::GrabNewConnection,
).await.map_err(|err| {
warn!("Moderator check failed: {:?}", err);
AdvancedCommonWebError::NotAuthorized
})?;
let limit = query.limit
.unwrap_or(DEFAULT_LIMIT)
.min(MAX_LIMIT);
let maybe_cursor_id = match &query.cursor {
None => None,
Some(cursor_str) => {
let decoded = server_state.opaque_cursors
.decode_cursor_expecting_name(CURSOR_NAME, cursor_str)
.map_err(|err| {
warn!("Failed to decode cursor: {:?}", err);
AdvancedCommonWebError::BadInputWithSimpleMessage(
"Invalid cursor".to_string())
})?;
decoded.last_id
}
};
let records = list_user_impersonation_requests_for_user(
ListUserImpersonationRequestsArgs {
user_token: path.user_token.as_str(),
maybe_cursor_id,
limit,
mysql_pool: &server_state.mysql_pool,
},
).await.map_err(|err| {
warn!("Failed to list impersonation requests: {:?}", err);
AdvancedCommonWebError::from_error(err)
})?;
let maybe_cursor = records.last().map(|last| {
server_state.opaque_cursors
.encode_last_id_cursor(CURSOR_NAME, last.id)
}).transpose().map_err(|err| {
warn!("Failed to encode cursor: {:?}", err);
AdvancedCommonWebError::server_error_with_message("Failed to encode cursor")
})?;
let impersonation_requests = records.into_iter().map(|r| {
UserImpersonationRequestResponse {
impersonator_user_token: r.impersonator_user_token,
impersonator_username: r.impersonator_username,
impersonator_display_name: r.impersonator_display_name,
impersonated_user_token: r.impersonated_user_token,
impersonated_username: r.impersonated_username,
impersonated_display_name: r.impersonated_display_name,
is_redeemed: r.is_redeemed,
is_expired: r.is_expired,
expires_at: r.expires_at,
created_at: r.created_at,
updated_at: r.updated_at,
}
}).collect();
Ok(Json(ListUserImpersonationRequestsSuccessResponse {
success: true,
impersonation_requests,
maybe_cursor,
}))
}
@@ -36,6 +36,7 @@ use crate::http_server::endpoints::moderation::user::moderator_user_lookup_by_st
use crate::http_server::endpoints::moderation::user::moderator_user_lookup_handler::moderator_user_lookup_handler;
use crate::http_server::endpoints::moderation::alerts::moderation_send_alert_handler::moderation_send_alert_handler;
use crate::http_server::endpoints::moderation::user_feature_flags::edit_user_feature_flags_handler::edit_user_feature_flags_handler;
use crate::http_server::endpoints::moderation::user_sessions::moderator_list_user_session_impersonation_requests_for_user_handler::moderator_list_user_session_impersonation_requests_for_user_handler;
use crate::http_server::endpoints::moderation::user_sessions::moderator_user_session_impersonation_request_handler::moderator_user_session_impersonation_request_handler;
pub fn add_moderator_routes<T, B> (app: App<T>) -> App<T>
@@ -109,6 +110,10 @@ pub fn add_moderator_routes<T, B> (app: App<T>) -> App<T>
.route(web::post().to(moderator_user_session_impersonation_request_handler))
.route(web::head().to(|| HttpResponse::Ok()))
)
.service(web::resource("/impersonation_requests/{user_token}")
.route(web::get().to(moderator_list_user_session_impersonation_requests_for_user_handler))
.route(web::head().to(|| HttpResponse::Ok()))
)
)
.service(web::resource("/wallet_ledger_entry/{wallet_ledger_entry_token}")
.route(web::get().to(moderator_get_wallet_ledger_entry_handler))
@@ -46,8 +46,6 @@ use bootstrap::bootstrap::{bootstrap, BootstrapArgs};
use chrono::Utc;
use cloud_storage::bucket_client::BucketClient;
use component_traits::traits::internal_user_lookup::InternalUserLookup;
use shared_env_var_config::logging::DEFAULT_RUST_LOG;
use shared_env_var_config::redis::env_get_redis_0_connection_string_or_default;
use elasticsearch::http::transport::Transport;
use elasticsearch::Elasticsearch;
use errors::AnyhowResult;
@@ -60,6 +58,8 @@ use mysql_queries::mediators::firehose_publisher::FirehosePublisher;
use redis::Client;
use redis_caching::redis_ttl_cache::RedisTtlCache;
use reusable_types::server_environment::ServerEnvironment;
use shared_env_var_config::logging::DEFAULT_RUST_LOG;
use shared_env_var_config::redis::env_get_redis_0_connection_string_or_default;
use tokio::runtime::Runtime;
use url_config::third_party_url_redirector::ThirdPartyUrlRedirector;
use user_traits_component::traits::internal_session_cache_purge::InternalSessionCachePurge;
@@ -71,16 +71,15 @@ use crate::billing::stripe_internal_user_lookup_impl::StripeInternalUserLookupIm
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 actix_artcraft::sessions::anonymous_visitor_tracking::avt_cookie_manager::AvtCookieManager;
use crate::http_server::middleware::error_alerting_middleware::error_alerting_middleware::ErrorAlertingMiddleware;
use crate::http_server::middleware::pushback_filter_middleware::PushbackFilter;
use crate::http_server::routes::add_routes::add_routes;
use crate::http_server::session::session_checker::SessionChecker;
use crate::http_server::web_utils::handle_multipart_error::handle_multipart_error;
use crate::http_server::web_utils::scoped_temp_dir_creator::ScopedTempDirCreator;
use crate::startup::build_pager::build_pager;
use crate::state::certs::google_sign_in_cert::GoogleSignInCert;
use crate::state::memory_cache::model_token_to_info_cache::ModelTokenToInfoCache;
use crate::startup::build_pager::build_pager;
use crate::state::server_state::{DurableInMemoryCaches, EnvConfig, EphemeralInMemoryCaches, FalData, InMemoryCaches, OpenAiData, ResendData, Seedance2ProData, ServerInfo, ServerState, StaticFeatureFlags, StripeSettings, TrollBans, WorldLabsData};
use crate::threads::db_health_checker_thread::db_health_check_status::HealthCheckStatus;
use crate::threads::db_health_checker_thread::db_health_checker_thread::db_health_checker_thread;
@@ -89,6 +88,8 @@ use crate::threads::poll_model_token_info_thread::poll_model_token_info_thread;
use crate::util::encrypted_sort_id::SortKeyCrypto;
use crate::util::troll_user_bans::load_troll_user_ban_list_from_directory::load_user_token_ban_list_from_directory;
use crate::util::troll_user_bans::troll_user_ban_list::TrollUserBanList;
use actix_artcraft::sessions::anonymous_visitor_tracking::avt_cookie_manager::AvtCookieManager;
use opaque_cursors::v2::opaque_cursor_encoder_v2::OpaqueCursorEncoderV2;
pub mod billing;
pub mod configs;
@@ -280,7 +281,9 @@ async fn main() -> AnyhowResult<()> {
// We can even rotate it without too much impact to users.
let sort_key_crypto_secret =
easyenv::get_env_string_or_default("SORT_KEY_SECRET", "webscale");
let sort_key_crypto = SortKeyCrypto::new(&sort_key_crypto_secret);
let opaque_cursor_encoder = OpaqueCursorEncoderV2::new(&sort_key_crypto_secret);
let health_check_interval = easyenv::get_env_duration_seconds_or_default(
"HEALTH_CHECK_INTERVAL_SECS", Duration::from_secs(3));
@@ -470,6 +473,7 @@ async fn main() -> AnyhowResult<()> {
auto_gc_bucket_client,
audio_uploads_bucket_root,
sort_key_crypto,
opaque_cursors: opaque_cursor_encoder,
static_api_token_set,
fal: FalData {
api_key: fal_api_key,
@@ -1,3 +1,22 @@
use crate::configs::app_startup::username_set::UsernameSet;
use crate::configs::static_api_tokens::StaticApiTokenSet;
use crate::http_server::deprecated_endpoints::categories::tts::list_fully_computed_assigned_tts_categories::list_fully_computed_assigned_tts_categories::ModelTokensByCategoryToken;
use crate::http_server::deprecated_endpoints::leaderboard::get_leaderboard::LeaderboardInfo;
use crate::http_server::endpoints::media_files::list::list_featured_media_files_handler::ListFeaturedMediaFilesQueryParams;
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::web_utils::redis_rate_limiter::RedisRateLimiter;
use crate::http_server::web_utils::scoped_temp_dir_creator::ScopedTempDirCreator;
use crate::state::certs::google_sign_in_cert::GoogleSignInCert;
use crate::state::flags::paging_flags::PagingFlags;
use crate::state::memory_cache::model_token_to_info_cache::ModelTokenToInfoCache;
use crate::threads::db_health_checker_thread::db_health_check_status::HealthCheckStatus;
use crate::util::encrypted_sort_id::SortKeyCrypto;
use crate::util::troll_user_bans::troll_user_ban_list::TrollUserBanList;
use actix_artcraft::sessions::anonymous_visitor_tracking::avt_cookie_manager::AvtCookieManager;
use actix_artcraft::sessions::user_sessions::http_user_session_manager::HttpUserSessionManager;
use actix_helpers::middleware::banned_cidr_filter::banned_cidr_set::BannedCidrSet;
use actix_helpers::middleware::banned_ip_filter::ip_ban_list::ip_ban_list::IpBanList;
@@ -9,7 +28,6 @@ use elasticsearch::Elasticsearch;
use fal_client::creds::fal_api_key::FalApiKey;
use memory_caching::arc_ttl_sieve::ArcTtlSieve;
use memory_caching::single_item_ttl_cache::SingleItemTtlCache;
use pager::client::pager::Pager;
use mysql_queries::mediators::badge_granter::BadgeGranter;
use mysql_queries::mediators::firehose_publisher::FirehosePublisher;
use mysql_queries::queries::generic_inference::web::get_pending_inference_job_count::InferenceQueueLengthResult;
@@ -17,31 +35,14 @@ use mysql_queries::queries::media_files::list::list_featured_media_files::Featur
use mysql_queries::queries::model_categories::list_categories_query_builder::CategoryList;
use mysql_queries::queries::tts::tts_inference_jobs::get_pending_tts_inference_job_count::TtsQueueLengthResult;
use mysql_queries::queries::w2l::w2l_templates::list_w2l_templates::W2lTemplateRecordForList;
use opaque_cursors::v2::opaque_cursor_encoder_v2::OpaqueCursorEncoderV2;
use pager::client::pager::Pager;
use redis::Client;
use redis_caching::redis_ttl_cache::RedisTtlCache;
use reusable_types::server_environment::ServerEnvironment;
use sqlx::MySqlPool;
use url_config::third_party_url_redirector::ThirdPartyUrlRedirector;
use crate::configs::app_startup::username_set::UsernameSet;
use crate::configs::static_api_tokens::StaticApiTokenSet;
use actix_artcraft::sessions::anonymous_visitor_tracking::avt_cookie_manager::AvtCookieManager;
use crate::http_server::deprecated_endpoints::categories::tts::list_fully_computed_assigned_tts_categories::list_fully_computed_assigned_tts_categories::ModelTokensByCategoryToken;
use crate::http_server::deprecated_endpoints::leaderboard::get_leaderboard::LeaderboardInfo;
use crate::http_server::endpoints::media_files::list::list_featured_media_files_handler::ListFeaturedMediaFilesQueryParams;
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::state::flags::paging_flags::PagingFlags;
use crate::http_server::web_utils::redis_rate_limiter::RedisRateLimiter;
use crate::http_server::web_utils::scoped_temp_dir_creator::ScopedTempDirCreator;
use crate::state::certs::google_sign_in_cert::GoogleSignInCert;
use crate::state::memory_cache::model_token_to_info_cache::ModelTokenToInfoCache;
use crate::threads::db_health_checker_thread::db_health_check_status::HealthCheckStatus;
use crate::util::encrypted_sort_id::SortKeyCrypto;
use crate::util::troll_user_bans::troll_user_ban_list::TrollUserBanList;
/// State that is injected into every endpoint.
pub struct ServerState {
/// Configuration from ENV vars.
@@ -108,6 +109,7 @@ pub struct ServerState {
pub audio_uploads_bucket_root: String,
pub sort_key_crypto: SortKeyCrypto,
pub opaque_cursors: OpaqueCursorEncoderV2,
pub ip_ban_list: IpBanList,