Merge upstream main into feat/500-api-key-ip-whitelist

This commit is contained in:
RWDai
2026-05-20 10:26:56 +08:00
501 changed files with 47013 additions and 3667 deletions
+6
View File
@@ -56,6 +56,7 @@ use crate::maintenance::spawn_request_candidate_cleanup_worker;
use crate::maintenance::spawn_stats_aggregation_worker;
use crate::maintenance::spawn_stats_hourly_aggregation_worker;
use crate::maintenance::spawn_usage_cleanup_worker;
use crate::maintenance::spawn_usage_counter_flush_worker;
use crate::maintenance::spawn_wallet_daily_usage_aggregation_worker;
const SYSTEM_CONFIG_CACHE_TTL: Duration = Duration::from_secs(3);
@@ -572,6 +573,7 @@ impl AppState {
}
pub(crate) fn invalidate_provider_routing_caches(&self) {
self.data.clear_minimal_candidate_selection_cache();
self.clear_provider_transport_snapshot_cache();
self.invalidate_scheduler_affinity_cache();
}
@@ -1144,6 +1146,10 @@ impl AppState {
crate::task_runtime::TASK_KEY_USAGE_QUEUE_WORKER,
self.usage_runtime.spawn_worker(self.data.clone()),
);
supervise_worker(
crate::task_runtime::TASK_KEY_USAGE_COUNTER_FLUSH,
spawn_usage_counter_flush_worker(self.data.clone()),
);
supervise_worker(
crate::task_runtime::TASK_KEY_PROVIDER_QUOTA_RESET,
crate::wallet_runtime::spawn_provider_quota_reset_worker(self.data.clone()),
@@ -33,6 +33,18 @@ impl AppState {
.map_err(|err| GatewayError::Internal(err.to_string()))
}
pub(crate) async fn list_required_unread_active_announcements(
&self,
user_id: &str,
now_unix_secs: u64,
limit: usize,
) -> Result<Vec<aether_data::repository::announcements::StoredAnnouncement>, GatewayError> {
self.data
.list_required_unread_active_announcements(user_id, now_unix_secs, limit)
.await
.map_err(|err| GatewayError::Internal(err.to_string()))
}
pub(crate) async fn create_announcement(
&self,
record: aether_data::repository::announcements::CreateAnnouncementRecord,
@@ -81,6 +81,14 @@ impl AppState {
pub(crate) async fn add_all_users_to_group(&self, group_id: &str) -> Result<(), GatewayError> {
for user in self.list_non_admin_export_users().await? {
let has_other_group = self
.list_user_groups_for_user(&user.id)
.await?
.into_iter()
.any(|group| group.id != group_id);
if has_other_group {
continue;
}
self.add_user_to_group(group_id, &user.id).await?;
}
Ok(())
@@ -16,6 +16,7 @@ mod candidate_queries;
mod gemini_files;
mod monitoring;
mod payments;
mod referrals;
mod security;
mod usage_queries;
mod user_preferences;
@@ -0,0 +1,241 @@
use crate::data::state::{
ReferralRelationshipListQuery, ReferralRelationshipRecord, ReferralRewardConfig,
ReferralRewardListQuery, ReferralRewardRecord, ReferralUserDashboard,
};
use crate::{AppState, GatewayError};
use axum::http::StatusCode;
fn referral_data_error(err: aether_data::DataLayerError) -> GatewayError {
match err {
aether_data::DataLayerError::InvalidInput(detail) => GatewayError::Client {
status: StatusCode::BAD_REQUEST,
message: detail,
},
other => GatewayError::Internal(other.to_string()),
}
}
fn config_bool(value: Option<&serde_json::Value>, default: bool) -> bool {
match value {
Some(serde_json::Value::Bool(value)) => *value,
Some(serde_json::Value::String(value)) => {
match value.trim().to_ascii_lowercase().as_str() {
"true" | "1" | "yes" | "on" => true,
"false" | "0" | "no" | "off" => false,
_ => default,
}
}
Some(serde_json::Value::Number(value)) => {
value.as_i64().map(|value| value != 0).unwrap_or(default)
}
_ => default,
}
}
fn config_string(value: Option<&serde_json::Value>) -> Option<String> {
match value {
Some(serde_json::Value::String(value)) => {
let value = value.trim();
(!value.is_empty()).then_some(value.to_string())
}
Some(value) => Some(value.to_string()),
None => None,
}
}
fn config_f64(value: Option<&serde_json::Value>, default: f64) -> f64 {
match value {
Some(serde_json::Value::Number(value)) => value.as_f64().unwrap_or(default),
Some(serde_json::Value::String(value)) => value.trim().parse::<f64>().unwrap_or(default),
_ => default,
}
}
impl AppState {
pub(crate) fn has_referral_data_backend(&self) -> bool {
self.data.has_referral_data_backend()
}
pub(crate) async fn record_user_privacy_policy_acceptance(
&self,
user_id: &str,
version: &str,
) -> Result<bool, GatewayError> {
self.data
.record_user_privacy_policy_acceptance(user_id, version)
.await
.map_err(|err| GatewayError::Internal(err.to_string()))
}
pub(crate) async fn referral_reward_config(
&self,
) -> Result<Option<ReferralRewardConfig>, GatewayError> {
let enabled = self
.read_system_config_json_value("referral_enabled")
.await?;
if !config_bool(enabled.as_ref(), false) {
return Ok(None);
}
let mode = self
.read_system_config_json_value("referral_reward_mode")
.await?;
let mode = config_string(mode.as_ref()).unwrap_or_else(|| "percent".to_string());
let percent = self
.read_system_config_json_value("referral_recharge_percent")
.await?;
let headcount_amount = self
.read_system_config_json_value("referral_headcount_amount_usd")
.await?;
let headcount_trigger = self
.read_system_config_json_value("referral_headcount_trigger")
.await?;
let headcount_trigger =
config_string(headcount_trigger.as_ref()).unwrap_or_else(|| "registration".to_string());
Ok(Some(ReferralRewardConfig {
percent_enabled: matches!(mode.as_str(), "percent" | "both"),
percent_rate: config_f64(percent.as_ref(), 0.0),
headcount_enabled: matches!(mode.as_str(), "headcount" | "both"),
headcount_amount_usd: config_f64(headcount_amount.as_ref(), 0.0),
headcount_trigger,
}))
}
pub(crate) async fn bind_referral_invite_after_registration(
&self,
user_id: &str,
email_verified: bool,
invite_code: Option<&str>,
source: Option<serde_json::Value>,
) -> Result<(), GatewayError> {
let Some(config) = self.referral_reward_config().await? else {
return Ok(());
};
let relationship = self
.data
.bind_referral_invite_code(user_id, invite_code, source)
.await
.map_err(referral_data_error)?;
let trigger_matches = config.headcount_trigger == "registration"
|| (config.headcount_trigger == "email_verified" && email_verified);
if relationship.is_some()
&& config.headcount_enabled
&& trigger_matches
&& config.headcount_amount_usd > 0.0
{
self.data
.apply_registration_referral_reward(
user_id,
config.headcount_amount_usd,
&config.headcount_trigger,
)
.await
.map_err(|err| GatewayError::Internal(err.to_string()))?;
}
Ok(())
}
pub(crate) async fn referral_dashboard(
&self,
user_id: &str,
) -> Result<Option<ReferralUserDashboard>, GatewayError> {
self.data
.referral_dashboard(user_id)
.await
.map_err(|err| GatewayError::Internal(err.to_string()))
}
pub(crate) async fn list_admin_referral_relationships(
&self,
query: ReferralRelationshipListQuery,
) -> Result<
Option<(
Vec<ReferralRelationshipRecord>,
u64,
crate::data::state::ReferralAdminStats,
)>,
GatewayError,
> {
self.data
.list_admin_referral_relationships(query)
.await
.map_err(|err| GatewayError::Internal(err.to_string()))
}
pub(crate) async fn list_admin_referral_rewards(
&self,
query: ReferralRewardListQuery,
) -> Result<
Option<(
Vec<ReferralRewardRecord>,
u64,
crate::data::state::ReferralAdminStats,
)>,
GatewayError,
> {
self.data
.list_admin_referral_rewards(query)
.await
.map_err(|err| GatewayError::Internal(err.to_string()))
}
pub(crate) async fn retry_referral_reward(
&self,
reward_id: &str,
operator_id: Option<&str>,
note: Option<&str>,
) -> Result<Option<ReferralRewardRecord>, GatewayError> {
self.data
.retry_referral_reward(reward_id, operator_id, note)
.await
.map_err(referral_data_error)
}
pub(crate) async fn void_referral_reward(
&self,
reward_id: &str,
operator_id: Option<&str>,
note: Option<&str>,
) -> Result<Option<ReferralRewardRecord>, GatewayError> {
self.data
.void_referral_reward(reward_id, operator_id, note)
.await
.map_err(referral_data_error)
}
pub(crate) async fn apply_referral_rewards_for_paid_order(
&self,
order: &aether_data::repository::wallet::StoredAdminPaymentOrder,
) -> Result<Vec<ReferralRewardRecord>, GatewayError> {
let Some(config) = self.referral_reward_config().await? else {
return Ok(Vec::new());
};
self.data
.apply_paid_order_referral_rewards(&order.id, config)
.await
.map_err(|err| GatewayError::Internal(err.to_string()))
}
pub(crate) async fn apply_referral_rewards_for_payment_order_id(
&self,
order_id: &str,
) -> Result<Vec<ReferralRewardRecord>, GatewayError> {
let Some(config) = self.referral_reward_config().await? else {
return Ok(Vec::new());
};
self.data
.apply_paid_order_referral_rewards(order_id, config)
.await
.map_err(|err| GatewayError::Internal(err.to_string()))
}
pub(crate) async fn reverse_referral_rewards_for_order(
&self,
order_id: &str,
amount_usd: f64,
) -> Result<Vec<ReferralRewardRecord>, GatewayError> {
self.data
.reverse_referral_rewards_for_order(order_id, amount_usd)
.await
.map_err(|err| GatewayError::Internal(err.to_string()))
}
}