mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-12 20:19:51 +08:00
refactor: 拆分 gateway 单体为独立 crate,新增 systemd 部署方案
将 gateway 内部的 model-fetch、provider-transport、scheduler-core、 usage-runtime、video-tasks-core 模块提取为独立 crate;重构 gateway 内部模块结构(state/router/cache/data/query 等);移除大量遗留模块 文件;新增 systemd 二进制部署骨架及相关文档;更新前端 usage 相关 API 和组件。
This commit is contained in:
@@ -0,0 +1,397 @@
|
||||
use super::{
|
||||
AdminBillingCollectorRecord, AdminBillingCollectorWriteInput, AdminBillingPresetApplyResult,
|
||||
AdminBillingRuleRecord, AdminBillingRuleWriteInput, AppState, GatewayError,
|
||||
LocalMutationOutcome,
|
||||
};
|
||||
use crate::query::billing as billing_query;
|
||||
|
||||
impl AppState {
|
||||
pub(crate) async fn admin_billing_enabled_default_value_exists(
|
||||
&self,
|
||||
api_format: &str,
|
||||
task_type: &str,
|
||||
dimension_name: &str,
|
||||
existing_id: Option<&str>,
|
||||
) -> Result<bool, GatewayError> {
|
||||
#[cfg(test)]
|
||||
if let Some(store) = self.admin_billing_collector_store.as_ref() {
|
||||
let exists = store
|
||||
.lock()
|
||||
.expect("admin billing collector store should lock")
|
||||
.values()
|
||||
.any(|collector| {
|
||||
collector.api_format == api_format
|
||||
&& collector.task_type == task_type
|
||||
&& collector.dimension_name == dimension_name
|
||||
&& collector.is_enabled
|
||||
&& collector.default_value.is_some()
|
||||
&& existing_id.is_none_or(|value| collector.id != value)
|
||||
});
|
||||
return Ok(exists);
|
||||
}
|
||||
|
||||
let Some(pool) = self.postgres_pool() else {
|
||||
return Ok(false);
|
||||
};
|
||||
billing_query::admin_billing_enabled_default_value_exists(
|
||||
&pool,
|
||||
api_format,
|
||||
task_type,
|
||||
dimension_name,
|
||||
existing_id,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn create_admin_billing_rule(
|
||||
&self,
|
||||
input: &AdminBillingRuleWriteInput,
|
||||
) -> Result<LocalMutationOutcome<AdminBillingRuleRecord>, GatewayError> {
|
||||
#[cfg(test)]
|
||||
if let Some(store) = self.admin_billing_rule_store.as_ref() {
|
||||
let now_unix_secs = chrono::Utc::now().timestamp().max(0) as u64;
|
||||
let record = AdminBillingRuleRecord {
|
||||
id: uuid::Uuid::new_v4().to_string(),
|
||||
name: input.name.clone(),
|
||||
task_type: input.task_type.clone(),
|
||||
global_model_id: input.global_model_id.clone(),
|
||||
model_id: input.model_id.clone(),
|
||||
expression: input.expression.clone(),
|
||||
variables: input.variables.clone(),
|
||||
dimension_mappings: input.dimension_mappings.clone(),
|
||||
is_enabled: input.is_enabled,
|
||||
created_at_unix_secs: now_unix_secs,
|
||||
updated_at_unix_secs: now_unix_secs,
|
||||
};
|
||||
store
|
||||
.lock()
|
||||
.expect("admin billing rule store should lock")
|
||||
.insert(record.id.clone(), record.clone());
|
||||
return Ok(LocalMutationOutcome::Applied(record));
|
||||
}
|
||||
|
||||
let Some(pool) = self.postgres_pool() else {
|
||||
return Ok(LocalMutationOutcome::Unavailable);
|
||||
};
|
||||
billing_query::create_admin_billing_rule(&pool, input).await
|
||||
}
|
||||
|
||||
pub(crate) async fn list_admin_billing_rules(
|
||||
&self,
|
||||
task_type: Option<&str>,
|
||||
is_enabled: Option<bool>,
|
||||
page: u32,
|
||||
page_size: u32,
|
||||
) -> Result<Option<(Vec<AdminBillingRuleRecord>, u64)>, GatewayError> {
|
||||
#[cfg(test)]
|
||||
if let Some(store) = self.admin_billing_rule_store.as_ref() {
|
||||
let mut items = store
|
||||
.lock()
|
||||
.expect("admin billing rule store should lock")
|
||||
.values()
|
||||
.filter(|record| {
|
||||
task_type.is_none_or(|expected| record.task_type == expected)
|
||||
&& is_enabled.is_none_or(|expected| record.is_enabled == expected)
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
items.sort_by(|left, right| {
|
||||
right
|
||||
.updated_at_unix_secs
|
||||
.cmp(&left.updated_at_unix_secs)
|
||||
.then_with(|| right.id.cmp(&left.id))
|
||||
});
|
||||
let total = items.len() as u64;
|
||||
let offset = (page.saturating_sub(1) as usize) * (page_size as usize);
|
||||
let items = items
|
||||
.into_iter()
|
||||
.skip(offset)
|
||||
.take(page_size as usize)
|
||||
.collect::<Vec<_>>();
|
||||
return Ok(Some((items, total)));
|
||||
}
|
||||
|
||||
let Some(pool) = self.postgres_pool() else {
|
||||
return Ok(None);
|
||||
};
|
||||
let (items, total) =
|
||||
billing_query::list_admin_billing_rules(&pool, task_type, is_enabled, page, page_size)
|
||||
.await?;
|
||||
Ok(Some((items, total)))
|
||||
}
|
||||
|
||||
pub(crate) async fn read_admin_billing_rule(
|
||||
&self,
|
||||
rule_id: &str,
|
||||
) -> Result<Option<AdminBillingRuleRecord>, GatewayError> {
|
||||
#[cfg(test)]
|
||||
if let Some(store) = self.admin_billing_rule_store.as_ref() {
|
||||
return Ok(store
|
||||
.lock()
|
||||
.expect("admin billing rule store should lock")
|
||||
.get(rule_id)
|
||||
.cloned());
|
||||
}
|
||||
|
||||
let Some(pool) = self.postgres_pool() else {
|
||||
return Ok(None);
|
||||
};
|
||||
billing_query::find_admin_billing_rule(&pool, rule_id).await
|
||||
}
|
||||
|
||||
pub(crate) async fn update_admin_billing_rule(
|
||||
&self,
|
||||
rule_id: &str,
|
||||
input: &AdminBillingRuleWriteInput,
|
||||
) -> Result<LocalMutationOutcome<AdminBillingRuleRecord>, GatewayError> {
|
||||
#[cfg(test)]
|
||||
if let Some(store) = self.admin_billing_rule_store.as_ref() {
|
||||
let mut guard = store.lock().expect("admin billing rule store should lock");
|
||||
let Some(record) = guard.get_mut(rule_id) else {
|
||||
return Ok(LocalMutationOutcome::NotFound);
|
||||
};
|
||||
record.name = input.name.clone();
|
||||
record.task_type = input.task_type.clone();
|
||||
record.global_model_id = input.global_model_id.clone();
|
||||
record.model_id = input.model_id.clone();
|
||||
record.expression = input.expression.clone();
|
||||
record.variables = input.variables.clone();
|
||||
record.dimension_mappings = input.dimension_mappings.clone();
|
||||
record.is_enabled = input.is_enabled;
|
||||
record.updated_at_unix_secs = chrono::Utc::now().timestamp().max(0) as u64;
|
||||
return Ok(LocalMutationOutcome::Applied(record.clone()));
|
||||
}
|
||||
|
||||
let Some(pool) = self.postgres_pool() else {
|
||||
return Ok(LocalMutationOutcome::Unavailable);
|
||||
};
|
||||
billing_query::update_admin_billing_rule(&pool, rule_id, input).await
|
||||
}
|
||||
|
||||
pub(crate) async fn create_admin_billing_collector(
|
||||
&self,
|
||||
input: &AdminBillingCollectorWriteInput,
|
||||
) -> Result<LocalMutationOutcome<AdminBillingCollectorRecord>, GatewayError> {
|
||||
#[cfg(test)]
|
||||
if let Some(store) = self.admin_billing_collector_store.as_ref() {
|
||||
let now_unix_secs = chrono::Utc::now().timestamp().max(0) as u64;
|
||||
let record = AdminBillingCollectorRecord {
|
||||
id: uuid::Uuid::new_v4().to_string(),
|
||||
api_format: input.api_format.clone(),
|
||||
task_type: input.task_type.clone(),
|
||||
dimension_name: input.dimension_name.clone(),
|
||||
source_type: input.source_type.clone(),
|
||||
source_path: input.source_path.clone(),
|
||||
value_type: input.value_type.clone(),
|
||||
transform_expression: input.transform_expression.clone(),
|
||||
default_value: input.default_value.clone(),
|
||||
priority: input.priority,
|
||||
is_enabled: input.is_enabled,
|
||||
created_at_unix_secs: now_unix_secs,
|
||||
updated_at_unix_secs: now_unix_secs,
|
||||
};
|
||||
store
|
||||
.lock()
|
||||
.expect("admin billing collector store should lock")
|
||||
.insert(record.id.clone(), record.clone());
|
||||
return Ok(LocalMutationOutcome::Applied(record));
|
||||
}
|
||||
|
||||
let Some(pool) = self.postgres_pool() else {
|
||||
return Ok(LocalMutationOutcome::Unavailable);
|
||||
};
|
||||
billing_query::create_admin_billing_collector(&pool, input).await
|
||||
}
|
||||
|
||||
pub(crate) async fn list_admin_billing_collectors(
|
||||
&self,
|
||||
api_format: Option<&str>,
|
||||
task_type: Option<&str>,
|
||||
dimension_name: Option<&str>,
|
||||
is_enabled: Option<bool>,
|
||||
page: u32,
|
||||
page_size: u32,
|
||||
) -> Result<Option<(Vec<AdminBillingCollectorRecord>, u64)>, GatewayError> {
|
||||
#[cfg(test)]
|
||||
if let Some(store) = self.admin_billing_collector_store.as_ref() {
|
||||
let mut items = store
|
||||
.lock()
|
||||
.expect("admin billing collector store should lock")
|
||||
.values()
|
||||
.filter(|record| {
|
||||
api_format.is_none_or(|expected| record.api_format == expected)
|
||||
&& task_type.is_none_or(|expected| record.task_type == expected)
|
||||
&& dimension_name.is_none_or(|expected| record.dimension_name == expected)
|
||||
&& is_enabled.is_none_or(|expected| record.is_enabled == expected)
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
items.sort_by(|left, right| {
|
||||
right
|
||||
.updated_at_unix_secs
|
||||
.cmp(&left.updated_at_unix_secs)
|
||||
.then_with(|| right.priority.cmp(&left.priority))
|
||||
.then_with(|| right.id.cmp(&left.id))
|
||||
});
|
||||
let total = items.len() as u64;
|
||||
let offset = (page.saturating_sub(1) as usize) * (page_size as usize);
|
||||
let items = items
|
||||
.into_iter()
|
||||
.skip(offset)
|
||||
.take(page_size as usize)
|
||||
.collect::<Vec<_>>();
|
||||
return Ok(Some((items, total)));
|
||||
}
|
||||
|
||||
let Some(pool) = self.postgres_pool() else {
|
||||
return Ok(None);
|
||||
};
|
||||
let (items, total) = billing_query::list_admin_billing_collectors(
|
||||
&pool,
|
||||
api_format,
|
||||
task_type,
|
||||
dimension_name,
|
||||
is_enabled,
|
||||
page,
|
||||
page_size,
|
||||
)
|
||||
.await?;
|
||||
Ok(Some((items, total)))
|
||||
}
|
||||
|
||||
pub(crate) async fn read_admin_billing_collector(
|
||||
&self,
|
||||
collector_id: &str,
|
||||
) -> Result<Option<AdminBillingCollectorRecord>, GatewayError> {
|
||||
#[cfg(test)]
|
||||
if let Some(store) = self.admin_billing_collector_store.as_ref() {
|
||||
return Ok(store
|
||||
.lock()
|
||||
.expect("admin billing collector store should lock")
|
||||
.get(collector_id)
|
||||
.cloned());
|
||||
}
|
||||
|
||||
let Some(pool) = self.postgres_pool() else {
|
||||
return Ok(None);
|
||||
};
|
||||
billing_query::find_admin_billing_collector(&pool, collector_id).await
|
||||
}
|
||||
|
||||
pub(crate) async fn update_admin_billing_collector(
|
||||
&self,
|
||||
collector_id: &str,
|
||||
input: &AdminBillingCollectorWriteInput,
|
||||
) -> Result<LocalMutationOutcome<AdminBillingCollectorRecord>, GatewayError> {
|
||||
#[cfg(test)]
|
||||
if let Some(store) = self.admin_billing_collector_store.as_ref() {
|
||||
let mut guard = store
|
||||
.lock()
|
||||
.expect("admin billing collector store should lock");
|
||||
let Some(record) = guard.get_mut(collector_id) else {
|
||||
return Ok(LocalMutationOutcome::NotFound);
|
||||
};
|
||||
record.api_format = input.api_format.clone();
|
||||
record.task_type = input.task_type.clone();
|
||||
record.dimension_name = input.dimension_name.clone();
|
||||
record.source_type = input.source_type.clone();
|
||||
record.source_path = input.source_path.clone();
|
||||
record.value_type = input.value_type.clone();
|
||||
record.transform_expression = input.transform_expression.clone();
|
||||
record.default_value = input.default_value.clone();
|
||||
record.priority = input.priority;
|
||||
record.is_enabled = input.is_enabled;
|
||||
record.updated_at_unix_secs = chrono::Utc::now().timestamp().max(0) as u64;
|
||||
return Ok(LocalMutationOutcome::Applied(record.clone()));
|
||||
}
|
||||
|
||||
let Some(pool) = self.postgres_pool() else {
|
||||
return Ok(LocalMutationOutcome::Unavailable);
|
||||
};
|
||||
billing_query::update_admin_billing_collector(&pool, collector_id, input).await
|
||||
}
|
||||
|
||||
pub(crate) async fn apply_admin_billing_preset(
|
||||
&self,
|
||||
preset: &str,
|
||||
mode: &str,
|
||||
collectors: &[AdminBillingCollectorWriteInput],
|
||||
) -> Result<LocalMutationOutcome<AdminBillingPresetApplyResult>, GatewayError> {
|
||||
#[cfg(test)]
|
||||
if let Some(store) = self.admin_billing_collector_store.as_ref() {
|
||||
let mut created = 0_u64;
|
||||
let mut updated = 0_u64;
|
||||
let mut skipped = 0_u64;
|
||||
let now_unix_secs = chrono::Utc::now().timestamp().max(0) as u64;
|
||||
let mut guard = store
|
||||
.lock()
|
||||
.expect("admin billing collector store should lock");
|
||||
for collector in collectors {
|
||||
let existing_id = guard
|
||||
.values()
|
||||
.find(|record| {
|
||||
record.api_format == collector.api_format
|
||||
&& record.task_type == collector.task_type
|
||||
&& record.dimension_name == collector.dimension_name
|
||||
&& record.priority == collector.priority
|
||||
&& record.is_enabled
|
||||
})
|
||||
.map(|record| record.id.clone());
|
||||
|
||||
match existing_id {
|
||||
Some(existing_id) if mode == "overwrite" => {
|
||||
if let Some(record) = guard.get_mut(&existing_id) {
|
||||
record.source_type = collector.source_type.clone();
|
||||
record.source_path = collector.source_path.clone();
|
||||
record.value_type = collector.value_type.clone();
|
||||
record.transform_expression = collector.transform_expression.clone();
|
||||
record.default_value = collector.default_value.clone();
|
||||
record.is_enabled = collector.is_enabled;
|
||||
record.updated_at_unix_secs = now_unix_secs;
|
||||
updated += 1;
|
||||
} else {
|
||||
skipped += 1;
|
||||
}
|
||||
}
|
||||
Some(_) => {
|
||||
skipped += 1;
|
||||
}
|
||||
None => {
|
||||
let record = AdminBillingCollectorRecord {
|
||||
id: uuid::Uuid::new_v4().to_string(),
|
||||
api_format: collector.api_format.clone(),
|
||||
task_type: collector.task_type.clone(),
|
||||
dimension_name: collector.dimension_name.clone(),
|
||||
source_type: collector.source_type.clone(),
|
||||
source_path: collector.source_path.clone(),
|
||||
value_type: collector.value_type.clone(),
|
||||
transform_expression: collector.transform_expression.clone(),
|
||||
default_value: collector.default_value.clone(),
|
||||
priority: collector.priority,
|
||||
is_enabled: collector.is_enabled,
|
||||
created_at_unix_secs: now_unix_secs,
|
||||
updated_at_unix_secs: now_unix_secs,
|
||||
};
|
||||
guard.insert(record.id.clone(), record);
|
||||
created += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Ok(LocalMutationOutcome::Applied(
|
||||
AdminBillingPresetApplyResult {
|
||||
preset: preset.to_string(),
|
||||
mode: mode.to_string(),
|
||||
created,
|
||||
updated,
|
||||
skipped,
|
||||
errors: Vec::new(),
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
let Some(pool) = self.postgres_pool() else {
|
||||
return Ok(LocalMutationOutcome::Unavailable);
|
||||
};
|
||||
billing_query::apply_admin_billing_preset(&pool, preset, mode, collectors).await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,464 @@
|
||||
use aether_data::repository::wallet::{
|
||||
AdminPaymentOrderListQuery, AdminWalletLedgerQuery, AdminWalletListQuery,
|
||||
AdminWalletRefundRequestListQuery, StoredAdminPaymentCallback, StoredAdminPaymentOrder,
|
||||
StoredAdminWalletLedgerItem, StoredAdminWalletListItem, StoredAdminWalletRefund,
|
||||
StoredAdminWalletRefundRequestItem, StoredAdminWalletTransaction,
|
||||
};
|
||||
|
||||
use crate::state::AdminPaymentCallbackRecord;
|
||||
use crate::{
|
||||
AdminWalletMutationOutcome, AdminWalletPaymentOrderRecord, AdminWalletRefundRecord, AppState,
|
||||
GatewayError,
|
||||
};
|
||||
|
||||
impl AppState {
|
||||
pub(crate) async fn list_admin_wallets(
|
||||
&self,
|
||||
status: Option<&str>,
|
||||
owner_type: Option<&str>,
|
||||
limit: usize,
|
||||
offset: usize,
|
||||
) -> Result<(Vec<StoredAdminWalletListItem>, u64), GatewayError> {
|
||||
let page = self
|
||||
.data
|
||||
.list_admin_wallets(&AdminWalletListQuery {
|
||||
status: status.map(ToOwned::to_owned),
|
||||
owner_type: owner_type.map(ToOwned::to_owned),
|
||||
limit,
|
||||
offset,
|
||||
})
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))?;
|
||||
Ok((page.items, page.total))
|
||||
}
|
||||
|
||||
pub(crate) async fn list_admin_wallet_ledger(
|
||||
&self,
|
||||
category: Option<&str>,
|
||||
reason_code: Option<&str>,
|
||||
owner_type: Option<&str>,
|
||||
limit: usize,
|
||||
offset: usize,
|
||||
) -> Result<(Vec<StoredAdminWalletLedgerItem>, u64), GatewayError> {
|
||||
let page = self
|
||||
.data
|
||||
.list_admin_wallet_ledger(&AdminWalletLedgerQuery {
|
||||
category: category.map(ToOwned::to_owned),
|
||||
reason_code: reason_code.map(ToOwned::to_owned),
|
||||
owner_type: owner_type.map(ToOwned::to_owned),
|
||||
limit,
|
||||
offset,
|
||||
})
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))?;
|
||||
Ok((page.items, page.total))
|
||||
}
|
||||
|
||||
pub(crate) async fn list_admin_payment_orders(
|
||||
&self,
|
||||
status: Option<&str>,
|
||||
payment_method: Option<&str>,
|
||||
limit: usize,
|
||||
offset: usize,
|
||||
) -> Result<Option<(Vec<AdminWalletPaymentOrderRecord>, u64)>, GatewayError> {
|
||||
#[cfg(test)]
|
||||
if let Some(store) = self.admin_wallet_payment_order_store.as_ref() {
|
||||
let now_unix_secs = chrono::Utc::now().timestamp().max(0) as u64;
|
||||
let mut items = store
|
||||
.lock()
|
||||
.expect("admin wallet payment order store should lock")
|
||||
.values()
|
||||
.filter(|order| {
|
||||
payment_method.is_none_or(|expected| order.payment_method == expected)
|
||||
&& status.is_none_or(|expected| {
|
||||
let effective_status = if order.status == "pending"
|
||||
&& order
|
||||
.expires_at_unix_secs
|
||||
.is_some_and(|value| value < now_unix_secs)
|
||||
{
|
||||
"expired"
|
||||
} else {
|
||||
order.status.as_str()
|
||||
};
|
||||
effective_status == expected
|
||||
})
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
items.sort_by(|left, right| {
|
||||
right
|
||||
.created_at_unix_secs
|
||||
.cmp(&left.created_at_unix_secs)
|
||||
.then_with(|| right.id.cmp(&left.id))
|
||||
});
|
||||
let total = items.len() as u64;
|
||||
let items = items
|
||||
.into_iter()
|
||||
.skip(offset)
|
||||
.take(limit)
|
||||
.collect::<Vec<_>>();
|
||||
return Ok(Some((items, total)));
|
||||
}
|
||||
|
||||
let page = self
|
||||
.data
|
||||
.list_admin_payment_orders(&AdminPaymentOrderListQuery {
|
||||
status: status.map(ToOwned::to_owned),
|
||||
payment_method: payment_method.map(ToOwned::to_owned),
|
||||
limit,
|
||||
offset,
|
||||
})
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))?;
|
||||
Ok(Some((
|
||||
page.items
|
||||
.into_iter()
|
||||
.map(stored_admin_payment_order_to_gateway)
|
||||
.collect(),
|
||||
page.total,
|
||||
)))
|
||||
}
|
||||
|
||||
pub(crate) async fn list_admin_payment_callbacks(
|
||||
&self,
|
||||
payment_method: Option<&str>,
|
||||
limit: usize,
|
||||
offset: usize,
|
||||
) -> Result<Option<(Vec<AdminPaymentCallbackRecord>, u64)>, GatewayError> {
|
||||
#[cfg(test)]
|
||||
if let Some(store) = self.admin_payment_callback_store.as_ref() {
|
||||
let mut items = store
|
||||
.lock()
|
||||
.expect("admin payment callback store should lock")
|
||||
.values()
|
||||
.filter(|callback| {
|
||||
payment_method.is_none_or(|expected| callback.payment_method == expected)
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
items.sort_by(|left, right| {
|
||||
right
|
||||
.created_at_unix_secs
|
||||
.cmp(&left.created_at_unix_secs)
|
||||
.then_with(|| right.id.cmp(&left.id))
|
||||
});
|
||||
let total = items.len() as u64;
|
||||
let items = items
|
||||
.into_iter()
|
||||
.skip(offset)
|
||||
.take(limit)
|
||||
.collect::<Vec<_>>();
|
||||
return Ok(Some((items, total)));
|
||||
}
|
||||
|
||||
let page = self
|
||||
.data
|
||||
.list_admin_payment_callbacks(payment_method, limit, offset)
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))?;
|
||||
Ok(Some((
|
||||
page.items
|
||||
.into_iter()
|
||||
.map(stored_admin_payment_callback_to_gateway)
|
||||
.collect(),
|
||||
page.total,
|
||||
)))
|
||||
}
|
||||
|
||||
pub(crate) async fn list_admin_wallet_transactions(
|
||||
&self,
|
||||
wallet_id: &str,
|
||||
limit: usize,
|
||||
offset: usize,
|
||||
) -> Result<(Vec<StoredAdminWalletTransaction>, u64), GatewayError> {
|
||||
#[cfg(test)]
|
||||
if let Some(store) = self.admin_wallet_transaction_store.as_ref() {
|
||||
let mut items = store
|
||||
.lock()
|
||||
.expect("admin wallet transaction store should lock")
|
||||
.values()
|
||||
.filter(|transaction| transaction.wallet_id == wallet_id)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
items.sort_by(|left, right| {
|
||||
right
|
||||
.created_at_unix_secs
|
||||
.cmp(&left.created_at_unix_secs)
|
||||
.then_with(|| right.id.cmp(&left.id))
|
||||
});
|
||||
let total = items.len() as u64;
|
||||
let items = items
|
||||
.into_iter()
|
||||
.skip(offset)
|
||||
.take(limit)
|
||||
.map(|record| StoredAdminWalletTransaction {
|
||||
id: record.id,
|
||||
wallet_id: record.wallet_id,
|
||||
category: record.category,
|
||||
reason_code: record.reason_code,
|
||||
amount: record.amount,
|
||||
balance_before: record.balance_before,
|
||||
balance_after: record.balance_after,
|
||||
recharge_balance_before: record.recharge_balance_before,
|
||||
recharge_balance_after: record.recharge_balance_after,
|
||||
gift_balance_before: record.gift_balance_before,
|
||||
gift_balance_after: record.gift_balance_after,
|
||||
link_type: record.link_type,
|
||||
link_id: record.link_id,
|
||||
operator_id: record.operator_id,
|
||||
operator_name: None,
|
||||
operator_email: None,
|
||||
description: record.description,
|
||||
created_at_unix_secs: Some(record.created_at_unix_secs),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
return Ok((items, total));
|
||||
}
|
||||
|
||||
let page = self
|
||||
.data
|
||||
.list_admin_wallet_transactions(wallet_id, limit, offset)
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))?;
|
||||
Ok((page.items, page.total))
|
||||
}
|
||||
|
||||
pub(crate) async fn list_admin_wallet_refunds(
|
||||
&self,
|
||||
wallet_id: &str,
|
||||
limit: usize,
|
||||
offset: usize,
|
||||
) -> Result<(Vec<AdminWalletRefundRecord>, u64), GatewayError> {
|
||||
#[cfg(test)]
|
||||
if let Some(store) = self.admin_wallet_refund_store.as_ref() {
|
||||
let mut items = store
|
||||
.lock()
|
||||
.expect("admin wallet refund store should lock")
|
||||
.values()
|
||||
.filter(|refund| refund.wallet_id == wallet_id)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
items.sort_by(|left, right| {
|
||||
right
|
||||
.created_at_unix_secs
|
||||
.cmp(&left.created_at_unix_secs)
|
||||
.then_with(|| right.id.cmp(&left.id))
|
||||
});
|
||||
let total = items.len() as u64;
|
||||
let items = items
|
||||
.into_iter()
|
||||
.skip(offset)
|
||||
.take(limit)
|
||||
.collect::<Vec<_>>();
|
||||
return Ok((items, total));
|
||||
}
|
||||
|
||||
let page = self
|
||||
.data
|
||||
.list_admin_wallet_refunds(wallet_id, limit, offset)
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))?;
|
||||
Ok((
|
||||
page.items
|
||||
.into_iter()
|
||||
.map(stored_admin_wallet_refund_to_gateway)
|
||||
.collect(),
|
||||
page.total,
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) async fn list_admin_wallet_refund_requests(
|
||||
&self,
|
||||
status: Option<&str>,
|
||||
limit: usize,
|
||||
offset: usize,
|
||||
) -> Result<(Vec<StoredAdminWalletRefundRequestItem>, u64), GatewayError> {
|
||||
#[cfg(test)]
|
||||
if let (Some(wallet_store), Some(refund_store)) = (
|
||||
self.auth_wallet_store.as_ref(),
|
||||
self.admin_wallet_refund_store.as_ref(),
|
||||
) {
|
||||
let wallets = wallet_store
|
||||
.lock()
|
||||
.expect("auth wallet store should lock")
|
||||
.clone();
|
||||
let mut items = refund_store
|
||||
.lock()
|
||||
.expect("admin wallet refund store should lock")
|
||||
.values()
|
||||
.filter(|refund| status.is_none_or(|expected| refund.status == expected))
|
||||
.filter(|refund| {
|
||||
wallets
|
||||
.get(&refund.wallet_id)
|
||||
.is_some_and(|wallet| wallet.user_id.is_some())
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
items.sort_by(|left, right| {
|
||||
right
|
||||
.created_at_unix_secs
|
||||
.cmp(&left.created_at_unix_secs)
|
||||
.then_with(|| right.id.cmp(&left.id))
|
||||
});
|
||||
let total = items.len() as u64;
|
||||
let items = items
|
||||
.into_iter()
|
||||
.skip(offset)
|
||||
.take(limit)
|
||||
.filter_map(|refund| {
|
||||
wallets.get(&refund.wallet_id).map(|wallet| {
|
||||
StoredAdminWalletRefundRequestItem {
|
||||
id: refund.id,
|
||||
refund_no: refund.refund_no,
|
||||
wallet_id: refund.wallet_id,
|
||||
user_id: refund.user_id,
|
||||
payment_order_id: refund.payment_order_id,
|
||||
source_type: refund.source_type,
|
||||
source_id: refund.source_id,
|
||||
refund_mode: refund.refund_mode,
|
||||
amount_usd: refund.amount_usd,
|
||||
status: refund.status,
|
||||
reason: refund.reason,
|
||||
failure_reason: refund.failure_reason,
|
||||
gateway_refund_id: refund.gateway_refund_id,
|
||||
payout_method: refund.payout_method,
|
||||
payout_reference: refund.payout_reference,
|
||||
payout_proof: refund.payout_proof,
|
||||
requested_by: refund.requested_by,
|
||||
approved_by: refund.approved_by,
|
||||
processed_by: refund.processed_by,
|
||||
wallet_user_id: wallet.user_id.clone(),
|
||||
wallet_user_name: None,
|
||||
wallet_api_key_id: wallet.api_key_id.clone(),
|
||||
api_key_name: None,
|
||||
wallet_status: wallet.status.clone(),
|
||||
created_at_unix_secs: Some(refund.created_at_unix_secs),
|
||||
updated_at_unix_secs: Some(refund.updated_at_unix_secs),
|
||||
processed_at_unix_secs: refund.processed_at_unix_secs,
|
||||
completed_at_unix_secs: refund.completed_at_unix_secs,
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
return Ok((items, total));
|
||||
}
|
||||
|
||||
let page = self
|
||||
.data
|
||||
.list_admin_wallet_refund_requests(&AdminWalletRefundRequestListQuery {
|
||||
status: status.map(ToOwned::to_owned),
|
||||
limit,
|
||||
offset,
|
||||
})
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))?;
|
||||
Ok((page.items, page.total))
|
||||
}
|
||||
|
||||
pub(crate) async fn read_admin_payment_order(
|
||||
&self,
|
||||
order_id: &str,
|
||||
) -> Result<AdminWalletMutationOutcome<AdminWalletPaymentOrderRecord>, GatewayError> {
|
||||
#[cfg(test)]
|
||||
if let Some(store) = self.admin_wallet_payment_order_store.as_ref() {
|
||||
return Ok(store
|
||||
.lock()
|
||||
.expect("admin wallet payment order store should lock")
|
||||
.get(order_id)
|
||||
.cloned()
|
||||
.map(AdminWalletMutationOutcome::Applied)
|
||||
.unwrap_or(AdminWalletMutationOutcome::NotFound));
|
||||
}
|
||||
|
||||
if !self.has_wallet_data_reader() {
|
||||
return Ok(AdminWalletMutationOutcome::Unavailable);
|
||||
}
|
||||
|
||||
let order = self
|
||||
.data
|
||||
.find_admin_payment_order(order_id)
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))?;
|
||||
match order {
|
||||
Some(record) => Ok(AdminWalletMutationOutcome::Applied(
|
||||
stored_admin_payment_order_to_gateway(record),
|
||||
)),
|
||||
None => Ok(AdminWalletMutationOutcome::NotFound),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn stored_admin_payment_order_to_gateway(
|
||||
record: StoredAdminPaymentOrder,
|
||||
) -> AdminWalletPaymentOrderRecord {
|
||||
AdminWalletPaymentOrderRecord {
|
||||
id: record.id,
|
||||
order_no: record.order_no,
|
||||
wallet_id: record.wallet_id,
|
||||
user_id: record.user_id,
|
||||
amount_usd: record.amount_usd,
|
||||
pay_amount: record.pay_amount,
|
||||
pay_currency: record.pay_currency,
|
||||
exchange_rate: record.exchange_rate,
|
||||
refunded_amount_usd: record.refunded_amount_usd,
|
||||
refundable_amount_usd: record.refundable_amount_usd,
|
||||
payment_method: record.payment_method,
|
||||
gateway_order_id: record.gateway_order_id,
|
||||
status: record.status,
|
||||
gateway_response: record.gateway_response,
|
||||
created_at_unix_secs: record.created_at_unix_secs,
|
||||
paid_at_unix_secs: record.paid_at_unix_secs,
|
||||
credited_at_unix_secs: record.credited_at_unix_secs,
|
||||
expires_at_unix_secs: record.expires_at_unix_secs,
|
||||
}
|
||||
}
|
||||
|
||||
fn stored_admin_payment_callback_to_gateway(
|
||||
record: StoredAdminPaymentCallback,
|
||||
) -> AdminPaymentCallbackRecord {
|
||||
AdminPaymentCallbackRecord {
|
||||
id: record.id,
|
||||
payment_order_id: record.payment_order_id,
|
||||
payment_method: record.payment_method,
|
||||
callback_key: record.callback_key,
|
||||
order_no: record.order_no,
|
||||
gateway_order_id: record.gateway_order_id,
|
||||
payload_hash: record.payload_hash,
|
||||
signature_valid: record.signature_valid,
|
||||
status: record.status,
|
||||
payload: record.payload,
|
||||
error_message: record.error_message,
|
||||
created_at_unix_secs: record.created_at_unix_secs,
|
||||
processed_at_unix_secs: record.processed_at_unix_secs,
|
||||
}
|
||||
}
|
||||
|
||||
fn stored_admin_wallet_refund_to_gateway(
|
||||
record: StoredAdminWalletRefund,
|
||||
) -> AdminWalletRefundRecord {
|
||||
AdminWalletRefundRecord {
|
||||
id: record.id,
|
||||
refund_no: record.refund_no,
|
||||
wallet_id: record.wallet_id,
|
||||
user_id: record.user_id,
|
||||
payment_order_id: record.payment_order_id,
|
||||
source_type: record.source_type,
|
||||
source_id: record.source_id,
|
||||
refund_mode: record.refund_mode,
|
||||
amount_usd: record.amount_usd,
|
||||
status: record.status,
|
||||
reason: record.reason,
|
||||
failure_reason: record.failure_reason,
|
||||
gateway_refund_id: record.gateway_refund_id,
|
||||
payout_method: record.payout_method,
|
||||
payout_reference: record.payout_reference,
|
||||
payout_proof: record.payout_proof,
|
||||
requested_by: record.requested_by,
|
||||
approved_by: record.approved_by,
|
||||
processed_by: record.processed_by,
|
||||
created_at_unix_secs: record.created_at_unix_secs,
|
||||
updated_at_unix_secs: record.updated_at_unix_secs,
|
||||
processed_at_unix_secs: record.processed_at_unix_secs,
|
||||
completed_at_unix_secs: record.completed_at_unix_secs,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
use super::super::{
|
||||
AdminBillingCollectorRecord, AdminBillingCollectorWriteInput, AdminBillingPresetApplyResult,
|
||||
AdminBillingRuleRecord, AdminBillingRuleWriteInput, AppState, GatewayError,
|
||||
LocalMutationOutcome,
|
||||
};
|
||||
|
||||
mod admin;
|
||||
mod finance_queries;
|
||||
Reference in New Issue
Block a user