mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-07 09:57:47 +08:00
Merge upstream main into feat/500-api-key-ip-whitelist
This commit is contained in:
@@ -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()))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user