Add multi-database data layer

Introduce aether-data-schema and driver-specific schema generation for Postgres, MySQL, and SQLite.

Split data backends, lifecycle, repositories, and gateway runtime integration across database drivers.

Verified with cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings, and cargo test --workspace.
This commit is contained in:
fawney19
2026-05-05 18:27:36 +08:00
parent 099653f732
commit fce7e959e5
372 changed files with 86217 additions and 21160 deletions

View File

@@ -4,6 +4,6 @@ pub(crate) use aether_data::repository::wallet::{
AdminWalletTransactionRecord,
};
pub(crate) use aether_data_contracts::repository::billing::{
AdminBillingCollectorRecord, AdminBillingCollectorWriteInput, AdminBillingPresetApplyResult,
AdminBillingRuleRecord, AdminBillingRuleWriteInput,
AdminBillingCollectorRecord, AdminBillingCollectorWriteInput, AdminBillingMutationOutcome,
AdminBillingPresetApplyResult, AdminBillingRuleRecord, AdminBillingRuleWriteInput,
};

View File

@@ -253,52 +253,39 @@ impl AppState {
Ok(self)
}
pub async fn run_postgres_migrations(&self) -> Result<bool, sqlx::migrate::MigrateError> {
let Some(pool) = self.postgres_pool() else {
return Ok(false);
};
aether_data::migrate::run_migrations(&pool).await?;
Ok(true)
pub async fn run_database_migrations(&self) -> Result<bool, sqlx::migrate::MigrateError> {
self.data.run_database_migrations().await
}
pub async fn run_postgres_backfills(&self) -> Result<bool, sqlx::migrate::MigrateError> {
let Some(pool) = self.postgres_pool() else {
return Ok(false);
};
aether_data::backfill::run_backfills(&pool).await?;
Ok(true)
pub async fn run_database_backfills(&self) -> Result<bool, sqlx::migrate::MigrateError> {
self.data.run_database_backfills().await
}
pub async fn pending_postgres_migrations(
pub async fn pending_database_migrations(
&self,
) -> Result<Option<Vec<aether_data::migrate::PendingMigrationInfo>>, sqlx::migrate::MigrateError>
{
let Some(pool) = self.postgres_pool() else {
return Ok(None);
};
Ok(Some(aether_data::migrate::pending_migrations(&pool).await?))
) -> Result<
Option<Vec<aether_data::lifecycle::migrate::PendingMigrationInfo>>,
sqlx::migrate::MigrateError,
> {
self.data.pending_database_migrations().await
}
pub async fn prepare_postgres_for_startup(
pub async fn prepare_database_for_startup(
&self,
) -> Result<Option<Vec<aether_data::migrate::PendingMigrationInfo>>, sqlx::migrate::MigrateError>
{
let Some(pool) = self.postgres_pool() else {
return Ok(None);
};
Ok(Some(
aether_data::migrate::prepare_database_for_startup(&pool).await?,
))
) -> Result<
Option<Vec<aether_data::lifecycle::migrate::PendingMigrationInfo>>,
sqlx::migrate::MigrateError,
> {
self.data.prepare_database_for_startup().await
}
pub async fn pending_postgres_backfills(
pub async fn pending_database_backfills(
&self,
) -> Result<Option<Vec<aether_data::backfill::PendingBackfillInfo>>, sqlx::migrate::MigrateError>
{
let Some(pool) = self.postgres_pool() else {
return Ok(None);
};
Ok(Some(aether_data::backfill::pending_backfills(&pool).await?))
) -> Result<
Option<Vec<aether_data::lifecycle::backfill::PendingBackfillInfo>>,
sqlx::migrate::MigrateError,
> {
self.data.pending_database_backfills().await
}
pub fn with_video_task_poller_config(mut self, interval: Duration, batch_size: usize) -> Self {
@@ -753,14 +740,10 @@ impl AppState {
self.data.has_redis_backend()
}
pub(crate) fn redis_kv_runner(&self) -> Option<aether_data::redis::RedisKvRunner> {
pub(crate) fn redis_kv_runner(&self) -> Option<aether_data::driver::redis::RedisKvRunner> {
self.data.kv_runner()
}
pub(crate) fn postgres_pool(&self) -> Option<aether_data::postgres::PostgresPool> {
self.data.postgres_pool()
}
pub(crate) fn remove_scheduler_affinity_cache_entry(&self, cache_key: &str) -> bool {
self.scheduler_affinity_cache.remove(cache_key).is_some()
}

View File

@@ -18,10 +18,10 @@ mod types;
mod video;
pub(crate) use self::admin_types::{
AdminBillingCollectorRecord, AdminBillingCollectorWriteInput, AdminBillingPresetApplyResult,
AdminBillingRuleRecord, AdminBillingRuleWriteInput, AdminPaymentCallbackRecord,
AdminSecurityBlacklistEntry, AdminWalletPaymentOrderRecord, AdminWalletRefundRecord,
AdminWalletTransactionRecord,
AdminBillingCollectorRecord, AdminBillingCollectorWriteInput, AdminBillingMutationOutcome,
AdminBillingPresetApplyResult, AdminBillingRuleRecord, AdminBillingRuleWriteInput,
AdminPaymentCallbackRecord, AdminSecurityBlacklistEntry, AdminWalletPaymentOrderRecord,
AdminWalletRefundRecord, AdminWalletTransactionRecord,
};
pub use self::app::AppState;
pub(crate) use self::cache::{

View File

@@ -155,6 +155,7 @@ impl AppState {
.map_err(|err| GatewayError::Internal(err.to_string()))
}
#[allow(clippy::too_many_arguments)]
pub(crate) async fn rotate_user_session_refresh_token(
&self,
user_id: &str,

View File

@@ -1,9 +1,21 @@
use super::{
AdminBillingCollectorRecord, AdminBillingCollectorWriteInput, AdminBillingPresetApplyResult,
AdminBillingRuleRecord, AdminBillingRuleWriteInput, AppState, GatewayError,
LocalMutationOutcome,
AdminBillingCollectorRecord, AdminBillingCollectorWriteInput, AdminBillingMutationOutcome,
AdminBillingPresetApplyResult, AdminBillingRuleRecord, AdminBillingRuleWriteInput, AppState,
GatewayError, LocalMutationOutcome,
};
use crate::query::billing as billing_query;
fn data_error(err: impl ToString) -> GatewayError {
GatewayError::Internal(err.to_string())
}
fn local_mutation_outcome<T>(outcome: AdminBillingMutationOutcome<T>) -> LocalMutationOutcome<T> {
match outcome {
AdminBillingMutationOutcome::Applied(value) => LocalMutationOutcome::Applied(value),
AdminBillingMutationOutcome::NotFound => LocalMutationOutcome::NotFound,
AdminBillingMutationOutcome::Invalid(detail) => LocalMutationOutcome::Invalid(detail),
AdminBillingMutationOutcome::Unavailable => LocalMutationOutcome::Unavailable,
}
}
impl AppState {
pub(crate) async fn admin_billing_enabled_default_value_exists(
@@ -30,17 +42,17 @@ impl AppState {
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
Ok(self
.data
.admin_billing_enabled_default_value_exists(
api_format,
task_type,
dimension_name,
existing_id,
)
.await
.map_err(data_error)?
.unwrap_or(false))
}
pub(crate) async fn create_admin_billing_rule(
@@ -70,10 +82,11 @@ impl AppState {
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
self.data
.create_admin_billing_rule(input)
.await
.map(local_mutation_outcome)
.map_err(data_error)
}
pub(crate) async fn list_admin_billing_rules(
@@ -111,13 +124,10 @@ impl AppState {
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)))
self.data
.list_admin_billing_rules(task_type, is_enabled, page, page_size)
.await
.map_err(data_error)
}
pub(crate) async fn read_admin_billing_rule(
@@ -133,10 +143,10 @@ impl AppState {
.cloned());
}
let Some(pool) = self.postgres_pool() else {
return Ok(None);
};
billing_query::find_admin_billing_rule(&pool, rule_id).await
self.data
.find_admin_billing_rule(rule_id)
.await
.map_err(data_error)
}
pub(crate) async fn update_admin_billing_rule(
@@ -162,10 +172,11 @@ impl AppState {
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
self.data
.update_admin_billing_rule(rule_id, input)
.await
.map(local_mutation_outcome)
.map_err(data_error)
}
pub(crate) async fn create_admin_billing_collector(
@@ -197,10 +208,11 @@ impl AppState {
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
self.data
.create_admin_billing_collector(input)
.await
.map(local_mutation_outcome)
.map_err(data_error)
}
pub(crate) async fn list_admin_billing_collectors(
@@ -243,20 +255,17 @@ impl AppState {
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)))
self.data
.list_admin_billing_collectors(
api_format,
task_type,
dimension_name,
is_enabled,
page,
page_size,
)
.await
.map_err(data_error)
}
pub(crate) async fn read_admin_billing_collector(
@@ -272,10 +281,10 @@ impl AppState {
.cloned());
}
let Some(pool) = self.postgres_pool() else {
return Ok(None);
};
billing_query::find_admin_billing_collector(&pool, collector_id).await
self.data
.find_admin_billing_collector(collector_id)
.await
.map_err(data_error)
}
pub(crate) async fn update_admin_billing_collector(
@@ -305,10 +314,11 @@ impl AppState {
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
self.data
.update_admin_billing_collector(collector_id, input)
.await
.map(local_mutation_outcome)
.map_err(data_error)
}
pub(crate) async fn apply_admin_billing_preset(
@@ -389,9 +399,10 @@ impl AppState {
));
}
let Some(pool) = self.postgres_pool() else {
return Ok(LocalMutationOutcome::Unavailable);
};
billing_query::apply_admin_billing_preset(&pool, preset, mode, collectors).await
self.data
.apply_admin_billing_preset(preset, mode, collectors)
.await
.map(local_mutation_outcome)
.map_err(data_error)
}
}

View File

@@ -1,7 +1,7 @@
use super::super::{
AdminBillingCollectorRecord, AdminBillingCollectorWriteInput, AdminBillingPresetApplyResult,
AdminBillingRuleRecord, AdminBillingRuleWriteInput, AppState, GatewayError,
LocalMutationOutcome,
AdminBillingCollectorRecord, AdminBillingCollectorWriteInput, AdminBillingMutationOutcome,
AdminBillingPresetApplyResult, AdminBillingRuleRecord, AdminBillingRuleWriteInput, AppState,
GatewayError, LocalMutationOutcome,
};
mod admin;

View File

@@ -13,6 +13,7 @@ mod auth;
mod billing;
mod candidate_queries;
mod gemini_files;
mod monitoring;
mod payments;
mod security;
mod usage_queries;
@@ -77,13 +78,21 @@ impl AppState {
self.data.has_wallet_writer()
}
pub fn has_database_wallet_data_writer(&self) -> bool {
self.data.has_wallet_writer() && self.data.database_driver().is_some()
}
pub fn has_auth_user_write_capability(&self) -> bool {
#[cfg(test)]
if self.auth_user_store.is_some() {
return true;
}
#[cfg(test)]
if !self.data.has_backends() {
return false;
}
self.postgres_pool().is_some()
self.data.has_user_reader()
}
pub fn has_auth_wallet_write_capability(&self) -> bool {
@@ -92,7 +101,7 @@ impl AppState {
return true;
}
self.postgres_pool().is_some()
self.data.has_wallet_writer()
}
pub fn has_provider_quota_data_writer(&self) -> bool {

View File

@@ -0,0 +1,136 @@
use std::collections::BTreeMap;
use aether_data::repository::audit::AuditLogListQuery;
use chrono::{DateTime, Utc};
use serde_json::{json, Value};
use super::{AppState, GatewayError};
impl AppState {
pub(crate) async fn list_admin_audit_logs(
&self,
cutoff_time: DateTime<Utc>,
username_pattern: Option<&str>,
event_type: Option<&str>,
limit: usize,
offset: usize,
) -> Result<(Vec<Value>, usize), GatewayError> {
let query = AuditLogListQuery {
cutoff_unix_secs: cutoff_unix_secs(cutoff_time),
username_pattern: username_pattern.map(str::to_string),
event_type: event_type.map(str::to_string),
limit,
offset,
};
let page = self
.data
.list_admin_audit_logs(&query)
.await
.map_err(|err| {
GatewayError::Internal(format!("admin audit logs read failed: {err}"))
})?;
let total = usize::try_from(page.total).unwrap_or(usize::MAX);
let items = page
.items
.iter()
.map(|record| {
json!({
"id": record.id,
"event_type": record.event_type,
"user_id": record.user_id,
"user_email": record.user_email,
"user_username": record.user_username,
"description": record.description,
"ip_address": record.ip_address,
"status_code": record.status_code,
"error_message": record.error_message,
"metadata": record.metadata,
"created_at": record.created_at_rfc3339(),
})
})
.collect();
Ok((items, total))
}
pub(crate) async fn list_admin_suspicious_activities(
&self,
cutoff_time: DateTime<Utc>,
) -> Result<Vec<Value>, GatewayError> {
let activities = self
.data
.list_admin_suspicious_activities(cutoff_unix_secs(cutoff_time))
.await
.map_err(|err| {
GatewayError::Internal(format!("admin suspicious activities read failed: {err}"))
})?;
Ok(activities
.iter()
.map(|record| {
json!({
"id": record.id,
"event_type": record.event_type,
"user_id": record.user_id,
"description": record.description,
"ip_address": record.ip_address,
"metadata": record.metadata,
"created_at": record.created_at_rfc3339(),
})
})
.collect())
}
pub(crate) async fn read_admin_user_behavior_event_counts(
&self,
user_id: &str,
cutoff_time: DateTime<Utc>,
) -> Result<BTreeMap<String, u64>, GatewayError> {
self.data
.read_admin_user_behavior_event_counts(user_id, cutoff_unix_secs(cutoff_time))
.await
.map_err(|err| {
GatewayError::Internal(format!("admin user behavior read failed: {err}"))
})
}
pub(crate) async fn list_user_audit_logs(
&self,
user_id: &str,
cutoff_time: DateTime<Utc>,
event_type: Option<&str>,
limit: usize,
offset: usize,
) -> Result<(Vec<Value>, usize), GatewayError> {
let query = AuditLogListQuery {
cutoff_unix_secs: cutoff_unix_secs(cutoff_time),
username_pattern: None,
event_type: event_type.map(str::to_string),
limit,
offset,
};
let page = self
.data
.list_user_audit_logs(user_id, &query)
.await
.map_err(|err| GatewayError::Internal(format!("user audit logs read failed: {err}")))?;
let total = usize::try_from(page.total).unwrap_or(usize::MAX);
let items = page
.items
.iter()
.map(|record| {
json!({
"id": record.id,
"event_type": record.event_type,
"description": record.description,
"ip_address": record.ip_address,
"status_code": record.status_code,
"created_at": record.created_at_rfc3339(),
})
})
.collect();
Ok((items, total))
}
}
fn cutoff_unix_secs(cutoff_time: DateTime<Utc>) -> u64 {
cutoff_time.timestamp().max(0) as u64
}

View File

@@ -1,10 +1,3 @@
use sqlx::Row;
use super::{
AdminBillingCollectorRecord, AdminBillingRuleRecord, AdminWalletPaymentOrderRecord,
AdminWalletRefundRecord, GatewayError,
};
pub(crate) fn admin_wallet_build_order_no(now: chrono::DateTime<chrono::Utc>) -> String {
format!(
"po_{}_{}",
@@ -21,274 +14,3 @@ pub(crate) fn admin_payment_gateway_response_map(
_ => serde_json::Map::new(),
}
}
pub(super) fn admin_wallet_snapshot_from_row(
row: &sqlx::postgres::PgRow,
) -> Result<aether_data::repository::wallet::StoredWalletSnapshot, GatewayError> {
aether_data::repository::wallet::StoredWalletSnapshot::new(
row.try_get("id")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
row.try_get("user_id")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
row.try_get("api_key_id")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
row.try_get("balance")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
row.try_get("gift_balance")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
row.try_get("limit_mode")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
row.try_get("currency")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
row.try_get("status")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
row.try_get("total_recharged")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
row.try_get("total_consumed")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
row.try_get("total_refunded")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
row.try_get("total_adjusted")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
row.try_get("updated_at_unix_secs")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
)
.map_err(|err| GatewayError::Internal(err.to_string()))
}
pub(super) fn admin_wallet_payment_order_from_row(
row: &sqlx::postgres::PgRow,
) -> Result<AdminWalletPaymentOrderRecord, GatewayError> {
Ok(AdminWalletPaymentOrderRecord {
id: row
.try_get("id")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
order_no: row
.try_get("order_no")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
wallet_id: row
.try_get("wallet_id")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
user_id: row
.try_get("user_id")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
amount_usd: row
.try_get("amount_usd")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
pay_amount: row
.try_get("pay_amount")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
pay_currency: row
.try_get("pay_currency")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
exchange_rate: row
.try_get("exchange_rate")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
refunded_amount_usd: row
.try_get("refunded_amount_usd")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
refundable_amount_usd: row
.try_get("refundable_amount_usd")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
payment_method: row
.try_get("payment_method")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
gateway_order_id: row
.try_get("gateway_order_id")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
status: row
.try_get("status")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
gateway_response: row
.try_get("gateway_response")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
created_at_unix_ms: row
.try_get::<i64, _>("created_at_unix_ms")
.map_err(|err| GatewayError::Internal(err.to_string()))?
.max(0) as u64,
paid_at_unix_secs: row
.try_get::<Option<i64>, _>("paid_at_unix_secs")
.map_err(|err| GatewayError::Internal(err.to_string()))?
.map(|value| value.max(0) as u64),
credited_at_unix_secs: row
.try_get::<Option<i64>, _>("credited_at_unix_secs")
.map_err(|err| GatewayError::Internal(err.to_string()))?
.map(|value| value.max(0) as u64),
expires_at_unix_secs: row
.try_get::<Option<i64>, _>("expires_at_unix_secs")
.map_err(|err| GatewayError::Internal(err.to_string()))?
.map(|value| value.max(0) as u64),
})
}
pub(super) fn admin_wallet_refund_from_row(
row: &sqlx::postgres::PgRow,
) -> Result<AdminWalletRefundRecord, GatewayError> {
Ok(AdminWalletRefundRecord {
id: row
.try_get("id")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
refund_no: row
.try_get("refund_no")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
wallet_id: row
.try_get("wallet_id")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
user_id: row
.try_get("user_id")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
payment_order_id: row
.try_get("payment_order_id")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
source_type: row
.try_get("source_type")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
source_id: row
.try_get("source_id")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
refund_mode: row
.try_get("refund_mode")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
amount_usd: row
.try_get("amount_usd")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
status: row
.try_get("status")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
reason: row
.try_get("reason")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
failure_reason: row
.try_get("failure_reason")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
gateway_refund_id: row
.try_get("gateway_refund_id")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
payout_method: row
.try_get("payout_method")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
payout_reference: row
.try_get("payout_reference")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
payout_proof: row
.try_get("payout_proof")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
requested_by: row
.try_get("requested_by")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
approved_by: row
.try_get("approved_by")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
processed_by: row
.try_get("processed_by")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
created_at_unix_ms: row
.try_get::<i64, _>("created_at_unix_ms")
.map_err(|err| GatewayError::Internal(err.to_string()))?
.max(0) as u64,
updated_at_unix_secs: row
.try_get::<i64, _>("updated_at_unix_secs")
.map_err(|err| GatewayError::Internal(err.to_string()))?
.max(0) as u64,
processed_at_unix_secs: row
.try_get::<Option<i64>, _>("processed_at_unix_secs")
.map_err(|err| GatewayError::Internal(err.to_string()))?
.map(|value| value.max(0) as u64),
completed_at_unix_secs: row
.try_get::<Option<i64>, _>("completed_at_unix_secs")
.map_err(|err| GatewayError::Internal(err.to_string()))?
.map(|value| value.max(0) as u64),
})
}
pub(super) fn admin_billing_rule_from_row(
row: &sqlx::postgres::PgRow,
) -> Result<AdminBillingRuleRecord, GatewayError> {
Ok(AdminBillingRuleRecord {
id: row
.try_get("id")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
name: row
.try_get("name")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
task_type: row
.try_get("task_type")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
global_model_id: row
.try_get("global_model_id")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
model_id: row
.try_get("model_id")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
expression: row
.try_get("expression")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
variables: row
.try_get::<Option<serde_json::Value>, _>("variables")
.map_err(|err| GatewayError::Internal(err.to_string()))?
.unwrap_or_else(|| serde_json::json!({})),
dimension_mappings: row
.try_get::<Option<serde_json::Value>, _>("dimension_mappings")
.map_err(|err| GatewayError::Internal(err.to_string()))?
.unwrap_or_else(|| serde_json::json!({})),
is_enabled: row
.try_get("is_enabled")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
created_at_unix_ms: row
.try_get::<i64, _>("created_at_unix_ms")
.map_err(|err| GatewayError::Internal(err.to_string()))?
.max(0) as u64,
updated_at_unix_secs: row
.try_get::<i64, _>("updated_at_unix_secs")
.map_err(|err| GatewayError::Internal(err.to_string()))?
.max(0) as u64,
})
}
pub(super) fn admin_billing_collector_from_row(
row: &sqlx::postgres::PgRow,
) -> Result<AdminBillingCollectorRecord, GatewayError> {
Ok(AdminBillingCollectorRecord {
id: row
.try_get("id")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
api_format: row
.try_get("api_format")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
task_type: row
.try_get("task_type")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
dimension_name: row
.try_get("dimension_name")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
source_type: row
.try_get("source_type")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
source_path: row
.try_get("source_path")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
value_type: row
.try_get("value_type")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
transform_expression: row
.try_get("transform_expression")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
default_value: row
.try_get("default_value")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
priority: row
.try_get("priority")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
is_enabled: row
.try_get("is_enabled")
.map_err(|err| GatewayError::Internal(err.to_string()))?,
created_at_unix_ms: row
.try_get::<i64, _>("created_at_unix_ms")
.map_err(|err| GatewayError::Internal(err.to_string()))?
.max(0) as u64,
updated_at_unix_secs: row
.try_get::<i64, _>("updated_at_unix_secs")
.map_err(|err| GatewayError::Internal(err.to_string()))?
.max(0) as u64,
})
}