Preserve usage data in system imports

This commit is contained in:
fawney19
2026-05-24 21:40:48 +08:00
parent 18d9004f22
commit e2d5fc9dfb
21 changed files with 2683 additions and 309 deletions
@@ -1017,6 +1017,26 @@ impl AuthApiKeyWriteRepository for InMemoryAuthApiKeySnapshotRepository {
Ok(Some(export.clone()))
}
async fn set_api_key_usage_totals(
&self,
api_key_id: &str,
total_requests: u64,
total_tokens: u64,
total_cost_usd: f64,
) -> Result<Option<StoredAuthApiKeyExportRecord>, DataLayerError> {
let mut index = self
.index
.write()
.expect("auth api key snapshot repository lock");
let Some(export) = index.export_by_api_key_id.get_mut(api_key_id) else {
return Ok(None);
};
export.total_requests = total_requests;
export.total_tokens = total_tokens;
export.total_cost_usd = total_cost_usd;
Ok(Some(export.clone()))
}
async fn delete_user_api_key(
&self,
user_id: &str,
@@ -692,6 +692,34 @@ WHERE id = ?
self.reload_export_by_id(api_key_id).await
}
async fn set_api_key_usage_totals(
&self,
api_key_id: &str,
total_requests: u64,
total_tokens: u64,
total_cost_usd: f64,
) -> Result<Option<StoredAuthApiKeyExportRecord>, DataLayerError> {
sqlx::query(
r#"
UPDATE api_keys
SET total_requests = ?,
total_tokens = ?,
total_cost_usd = ?,
updated_at = ?
WHERE id = ?
"#,
)
.bind(total_requests as i64)
.bind(total_tokens as i64)
.bind(total_cost_usd)
.bind(current_unix_secs() as i64)
.bind(api_key_id)
.execute(&self.pool)
.await
.map_sql_err()?;
self.reload_export_by_id(api_key_id).await
}
async fn delete_user_api_key(
&self,
user_id: &str,
@@ -668,6 +668,40 @@ RETURNING
is_standalone
"#;
const SET_API_KEY_USAGE_TOTALS_SQL: &str = r#"
UPDATE api_keys
SET
total_requests = $2,
total_tokens = $3,
total_cost_usd = $4,
updated_at = NOW()
WHERE id = $1
RETURNING
user_id,
id AS api_key_id,
key_hash,
key_encrypted,
name,
allowed_providers,
allowed_api_formats,
allowed_models,
ip_rules,
rate_limit,
concurrent_limit,
force_capabilities,
feature_settings,
is_active,
CAST(EXTRACT(EPOCH FROM expires_at) AS BIGINT) AS expires_at_unix_secs,
auto_delete_on_expiry,
total_requests,
COALESCE(total_tokens, 0)::BIGINT AS total_tokens,
COALESCE(CAST(total_cost_usd AS DOUBLE PRECISION), 0) AS total_cost_usd,
CAST(EXTRACT(EPOCH FROM last_used_at) AS BIGINT) AS last_used_at_unix_secs,
CAST(EXTRACT(EPOCH FROM created_at) AS BIGINT) AS created_at_unix_secs,
CAST(EXTRACT(EPOCH FROM updated_at) AS BIGINT) AS updated_at_unix_secs,
is_standalone
"#;
const SET_USER_API_KEY_LOCKED_SQL: &str = r#"
UPDATE api_keys
SET
@@ -1436,6 +1470,24 @@ impl AuthApiKeyWriteRepository for SqlxAuthApiKeySnapshotReadRepository {
.find(|record| record.user_id == user_id && !record.is_standalone))
}
async fn set_api_key_usage_totals(
&self,
api_key_id: &str,
total_requests: u64,
total_tokens: u64,
total_cost_usd: f64,
) -> Result<Option<StoredAuthApiKeyExportRecord>, DataLayerError> {
let row = sqlx::query(SET_API_KEY_USAGE_TOTALS_SQL)
.bind(api_key_id)
.bind(total_requests as i64)
.bind(total_tokens as i64)
.bind(total_cost_usd)
.fetch_optional(&self.pool)
.await
.map_postgres_err()?;
row.as_ref().map(map_auth_api_key_export_row).transpose()
}
async fn delete_user_api_key(
&self,
user_id: &str,
@@ -692,6 +692,34 @@ WHERE id = ?
self.reload_export_by_id(api_key_id).await
}
async fn set_api_key_usage_totals(
&self,
api_key_id: &str,
total_requests: u64,
total_tokens: u64,
total_cost_usd: f64,
) -> Result<Option<StoredAuthApiKeyExportRecord>, DataLayerError> {
sqlx::query(
r#"
UPDATE api_keys
SET total_requests = ?,
total_tokens = ?,
total_cost_usd = ?,
updated_at = ?
WHERE id = ?
"#,
)
.bind(total_requests as i64)
.bind(total_tokens as i64)
.bind(total_cost_usd)
.bind(current_unix_secs() as i64)
.bind(api_key_id)
.execute(&self.pool)
.await
.map_sql_err()?;
self.reload_export_by_id(api_key_id).await
}
async fn delete_user_api_key(
&self,
user_id: &str,
@@ -691,6 +691,14 @@ pub trait AuthApiKeyWriteRepository: Send + Sync {
feature_settings: Option<serde_json::Value>,
) -> Result<Option<StoredAuthApiKeyExportRecord>, crate::DataLayerError>;
async fn set_api_key_usage_totals(
&self,
api_key_id: &str,
total_requests: u64,
total_tokens: u64,
total_cost_usd: f64,
) -> Result<Option<StoredAuthApiKeyExportRecord>, crate::DataLayerError>;
async fn delete_user_api_key(
&self,
user_id: &str,
@@ -21,6 +21,85 @@ pub struct AdminSystemStats {
pub total_requests: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AdminSystemUsageAggregateImportMode {
Skip,
Overwrite,
Error,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct AdminSystemStatsDailyAggregate {
pub date_unix_secs: u64,
pub total_requests: u64,
pub success_requests: u64,
pub error_requests: u64,
pub input_tokens: u64,
pub output_tokens: u64,
pub cache_creation_tokens: u64,
pub cache_read_tokens: u64,
pub total_cost: f64,
pub actual_total_cost: f64,
pub is_complete: bool,
pub aggregated_at_unix_secs: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct AdminSystemStatsUserDailyAggregate {
pub user_id: String,
pub username: Option<String>,
pub date_unix_secs: u64,
pub total_requests: u64,
pub success_requests: u64,
pub error_requests: u64,
pub input_tokens: u64,
pub output_tokens: u64,
pub cache_creation_tokens: u64,
pub cache_read_tokens: u64,
pub total_cost: f64,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct AdminSystemStatsDailyApiKeyAggregate {
pub api_key_id: String,
pub api_key_name: Option<String>,
pub date_unix_secs: u64,
pub total_requests: u64,
pub success_requests: u64,
pub error_requests: u64,
pub input_tokens: u64,
pub output_tokens: u64,
pub cache_creation_tokens: u64,
pub cache_read_tokens: u64,
pub total_cost: f64,
}
#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct AdminSystemUsageAggregateSnapshot {
#[serde(default)]
pub stats_daily: Vec<AdminSystemStatsDailyAggregate>,
#[serde(default)]
pub stats_user_daily: Vec<AdminSystemStatsUserDailyAggregate>,
#[serde(default)]
pub stats_daily_api_key: Vec<AdminSystemStatsDailyApiKeyAggregate>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct AdminSystemUsageAggregateImportCounter {
pub created: u64,
pub updated: u64,
pub skipped: u64,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct AdminSystemUsageAggregateImportSummary {
pub stats_daily: AdminSystemUsageAggregateImportCounter,
pub stats_user_daily: AdminSystemUsageAggregateImportCounter,
pub stats_daily_api_key: AdminSystemUsageAggregateImportCounter,
pub skipped_unmapped_user_daily: u64,
pub skipped_unmapped_api_key_daily: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AdminSystemPurgeTarget {
Config,