mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 17:30:23 +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:
@@ -1,4 +1,5 @@
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::body::{Body, Bytes};
|
||||
use axum::http::Response;
|
||||
|
||||
|
||||
@@ -5,8 +5,9 @@ use super::adaptive_shared::{
|
||||
admin_adaptive_key_not_found_response, admin_adaptive_key_payload,
|
||||
admin_adaptive_load_candidate_keys,
|
||||
};
|
||||
use crate::gateway::handlers::{query_param_value, unix_secs_to_rfc3339};
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::{query_param_value, unix_secs_to_rfc3339};
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
http,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::gateway::handlers::json_string_list;
|
||||
use crate::gateway::{AppState, GatewayError};
|
||||
use crate::handlers::json_string_list;
|
||||
use crate::{AppState, GatewayError};
|
||||
use aether_data::repository::provider_catalog::StoredProviderCatalogKey;
|
||||
use axum::{
|
||||
body::Body,
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
use crate::gateway::handlers::admin::{
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::admin::{
|
||||
default_admin_user_api_key_name, format_optional_unix_secs_iso8601,
|
||||
generate_admin_user_api_key_plaintext, hash_admin_user_api_key, masked_user_api_key_display,
|
||||
normalize_admin_optional_api_key_name, normalize_admin_user_api_formats,
|
||||
normalize_admin_user_string_list,
|
||||
};
|
||||
use crate::gateway::handlers::public::serialize_admin_system_users_export_wallet;
|
||||
use crate::gateway::handlers::{
|
||||
use crate::handlers::public::serialize_admin_system_users_export_wallet;
|
||||
use crate::handlers::{
|
||||
decrypt_catalog_secret_with_fallbacks, encrypt_catalog_secret_with_fallbacks, query_param_bool,
|
||||
query_param_optional_bool, query_param_value,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::Body,
|
||||
http,
|
||||
|
||||
@@ -11,7 +11,9 @@ use super::{
|
||||
hash_admin_user_api_key, masked_user_api_key_display, normalize_admin_optional_api_key_name,
|
||||
normalize_admin_user_api_formats, normalize_admin_user_string_list,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::admin::misc_helpers::attach_admin_audit_response;
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::Body,
|
||||
http,
|
||||
@@ -111,22 +113,28 @@ pub(super) async fn build_admin_create_api_key_response(
|
||||
return Ok(build_admin_api_keys_data_unavailable_response());
|
||||
};
|
||||
|
||||
Ok(Json(json!({
|
||||
"id": created.api_key_id,
|
||||
"key": plaintext_key,
|
||||
"name": created.name,
|
||||
"key_display": masked_user_api_key_display(state, created.key_encrypted.as_deref()),
|
||||
"is_standalone": true,
|
||||
"is_active": created.is_active,
|
||||
"rate_limit": created.rate_limit,
|
||||
"allowed_providers": created.allowed_providers,
|
||||
"allowed_api_formats": created.allowed_api_formats,
|
||||
"allowed_models": created.allowed_models,
|
||||
"expires_at": format_optional_unix_secs_iso8601(created.expires_at_unix_secs),
|
||||
"wallet": serde_json::Value::Null,
|
||||
"message": "独立余额Key创建成功,请妥善保存完整密钥,后续将无法查看",
|
||||
}))
|
||||
.into_response())
|
||||
Ok(attach_admin_audit_response(
|
||||
Json(json!({
|
||||
"id": created.api_key_id,
|
||||
"key": plaintext_key,
|
||||
"name": created.name,
|
||||
"key_display": masked_user_api_key_display(state, created.key_encrypted.as_deref()),
|
||||
"is_standalone": true,
|
||||
"is_active": created.is_active,
|
||||
"rate_limit": created.rate_limit,
|
||||
"allowed_providers": created.allowed_providers,
|
||||
"allowed_api_formats": created.allowed_api_formats,
|
||||
"allowed_models": created.allowed_models,
|
||||
"expires_at": format_optional_unix_secs_iso8601(created.expires_at_unix_secs),
|
||||
"wallet": serde_json::Value::Null,
|
||||
"message": "独立余额Key创建成功,请妥善保存完整密钥,后续将无法查看",
|
||||
}))
|
||||
.into_response(),
|
||||
"admin_standalone_api_key_created",
|
||||
"create_standalone_api_key",
|
||||
"api_key",
|
||||
&created.api_key_id,
|
||||
))
|
||||
}
|
||||
|
||||
pub(super) async fn build_admin_update_api_key_response(
|
||||
@@ -244,7 +252,13 @@ pub(super) async fn build_admin_update_api_key_response(
|
||||
let mut payload =
|
||||
build_admin_api_key_detail_payload(state, &updated, total_tokens, wallet.as_ref());
|
||||
payload["message"] = json!("API密钥已更新");
|
||||
Ok(Json(payload).into_response())
|
||||
Ok(attach_admin_audit_response(
|
||||
Json(payload).into_response(),
|
||||
"admin_standalone_api_key_updated",
|
||||
"update_standalone_api_key",
|
||||
"api_key",
|
||||
&api_key_id,
|
||||
))
|
||||
}
|
||||
|
||||
pub(super) async fn build_admin_toggle_api_key_response(
|
||||
@@ -295,12 +309,18 @@ pub(super) async fn build_admin_toggle_api_key_response(
|
||||
return Ok(build_admin_api_keys_not_found_response());
|
||||
};
|
||||
|
||||
Ok(Json(json!({
|
||||
"id": updated.api_key_id,
|
||||
"is_active": updated.is_active,
|
||||
"message": if updated.is_active { "API密钥已启用" } else { "API密钥已禁用" },
|
||||
}))
|
||||
.into_response())
|
||||
Ok(attach_admin_audit_response(
|
||||
Json(json!({
|
||||
"id": updated.api_key_id,
|
||||
"is_active": updated.is_active,
|
||||
"message": if updated.is_active { "API密钥已启用" } else { "API密钥已禁用" },
|
||||
}))
|
||||
.into_response(),
|
||||
"admin_standalone_api_key_toggled",
|
||||
"toggle_standalone_api_key",
|
||||
"api_key",
|
||||
&api_key_id,
|
||||
))
|
||||
}
|
||||
|
||||
pub(super) async fn build_admin_delete_api_key_response(
|
||||
@@ -316,7 +336,13 @@ pub(super) async fn build_admin_delete_api_key_response(
|
||||
};
|
||||
|
||||
match state.delete_standalone_api_key(&api_key_id).await? {
|
||||
true => Ok(Json(json!({ "message": "API密钥已删除" })).into_response()),
|
||||
true => Ok(attach_admin_audit_response(
|
||||
Json(json!({ "message": "API密钥已删除" })).into_response(),
|
||||
"admin_standalone_api_key_deleted",
|
||||
"delete_standalone_api_key",
|
||||
"api_key",
|
||||
&api_key_id,
|
||||
)),
|
||||
false => Ok(build_admin_api_keys_not_found_response()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ use super::admin_api_keys_shared::{
|
||||
build_admin_api_keys_data_unavailable_response, build_admin_api_keys_not_found_response,
|
||||
};
|
||||
use super::{decrypt_catalog_secret_with_fallbacks, query_param_bool, query_param_optional_bool};
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::admin::misc_helpers::attach_admin_audit_response;
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::Body,
|
||||
http,
|
||||
@@ -119,7 +121,13 @@ pub(super) async fn build_admin_api_key_detail_response(
|
||||
)
|
||||
.into_response());
|
||||
};
|
||||
return Ok(Json(json!({ "key": key })).into_response());
|
||||
return Ok(attach_admin_audit_response(
|
||||
Json(json!({ "key": key })).into_response(),
|
||||
"admin_standalone_api_key_revealed",
|
||||
"reveal_standalone_api_key",
|
||||
"api_key",
|
||||
&api_key_id,
|
||||
));
|
||||
}
|
||||
|
||||
let wallet = state
|
||||
|
||||
@@ -6,7 +6,8 @@ use super::admin_api_keys_read_routes::{
|
||||
build_admin_api_key_detail_response, build_admin_list_api_keys_response,
|
||||
};
|
||||
use super::admin_api_keys_shared::build_admin_api_keys_data_unavailable_response;
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{body::Body, http, response::Response};
|
||||
|
||||
pub(super) async fn maybe_build_local_admin_api_keys_routes_response(
|
||||
|
||||
@@ -3,7 +3,7 @@ use super::{
|
||||
serialize_admin_system_users_export_wallet, AppState, Body, GatewayError,
|
||||
GatewayPublicRequestContext, IntoResponse, Json, Response,
|
||||
};
|
||||
use crate::gateway::handlers::admin::api_keys::ADMIN_API_KEYS_DATA_UNAVAILABLE_DETAIL;
|
||||
use crate::handlers::admin::api_keys::ADMIN_API_KEYS_DATA_UNAVAILABLE_DETAIL;
|
||||
|
||||
#[derive(Debug, Default, serde::Deserialize)]
|
||||
pub(super) struct AdminStandaloneApiKeyCreateRequest {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::gateway::handlers::{query_param_value, unix_secs_to_rfc3339};
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::{query_param_value, unix_secs_to_rfc3339};
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
http,
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use super::{
|
||||
admin_billing_optional_bool_filter, admin_billing_optional_epoch_value,
|
||||
admin_billing_optional_filter, admin_billing_pages, admin_billing_parse_page,
|
||||
admin_billing_parse_page_size, admin_billing_validate_safe_expression,
|
||||
build_admin_billing_bad_request_response, build_admin_billing_not_found_response,
|
||||
build_admin_billing_read_only_response, default_admin_billing_true,
|
||||
normalize_admin_billing_optional_text, normalize_admin_billing_required_text,
|
||||
admin_billing_optional_bool_filter, admin_billing_optional_filter, admin_billing_pages,
|
||||
admin_billing_parse_page, admin_billing_parse_page_size,
|
||||
admin_billing_validate_safe_expression, build_admin_billing_bad_request_response,
|
||||
build_admin_billing_not_found_response, build_admin_billing_read_only_response,
|
||||
default_admin_billing_true, normalize_admin_billing_optional_text,
|
||||
normalize_admin_billing_required_text,
|
||||
};
|
||||
use crate::gateway::handlers::unix_secs_to_rfc3339;
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::unix_secs_to_rfc3339;
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
http,
|
||||
@@ -16,7 +17,6 @@ use axum::{
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use sqlx::Row;
|
||||
|
||||
fn default_admin_billing_collector_value_type() -> String {
|
||||
"float".to_string()
|
||||
@@ -43,7 +43,7 @@ struct AdminBillingCollectorUpsertRequest {
|
||||
}
|
||||
|
||||
fn build_admin_billing_collector_payload_from_record(
|
||||
record: &crate::gateway::AdminBillingCollectorRecord,
|
||||
record: &crate::AdminBillingCollectorRecord,
|
||||
) -> serde_json::Value {
|
||||
json!({
|
||||
"id": record.id,
|
||||
@@ -75,26 +75,6 @@ fn admin_billing_collector_id_from_path(request_path: &str) -> Option<String> {
|
||||
}
|
||||
}
|
||||
|
||||
fn admin_billing_collector_payload(
|
||||
row: &sqlx::postgres::PgRow,
|
||||
) -> Result<serde_json::Value, GatewayError> {
|
||||
Ok(json!({
|
||||
"id": row.try_get::<String, _>("id").map_err(|err| GatewayError::Internal(err.to_string()))?,
|
||||
"api_format": row.try_get::<String, _>("api_format").map_err(|err| GatewayError::Internal(err.to_string()))?,
|
||||
"task_type": row.try_get::<String, _>("task_type").map_err(|err| GatewayError::Internal(err.to_string()))?,
|
||||
"dimension_name": row.try_get::<String, _>("dimension_name").map_err(|err| GatewayError::Internal(err.to_string()))?,
|
||||
"source_type": row.try_get::<String, _>("source_type").map_err(|err| GatewayError::Internal(err.to_string()))?,
|
||||
"source_path": row.try_get::<Option<String>, _>("source_path").map_err(|err| GatewayError::Internal(err.to_string()))?,
|
||||
"value_type": row.try_get::<String, _>("value_type").map_err(|err| GatewayError::Internal(err.to_string()))?,
|
||||
"transform_expression": row.try_get::<Option<String>, _>("transform_expression").map_err(|err| GatewayError::Internal(err.to_string()))?,
|
||||
"default_value": row.try_get::<Option<String>, _>("default_value").map_err(|err| GatewayError::Internal(err.to_string()))?,
|
||||
"priority": row.try_get::<i32, _>("priority").map_err(|err| GatewayError::Internal(err.to_string()))?,
|
||||
"is_enabled": row.try_get::<bool, _>("is_enabled").map_err(|err| GatewayError::Internal(err.to_string()))?,
|
||||
"created_at": admin_billing_optional_epoch_value(row, "created_at_unix_secs")?,
|
||||
"updated_at": admin_billing_optional_epoch_value(row, "updated_at_unix_secs")?,
|
||||
}))
|
||||
}
|
||||
|
||||
async fn build_admin_list_dimension_collectors_response(
|
||||
state: &AppState,
|
||||
request_context: &GatewayPublicRequestContext,
|
||||
@@ -116,7 +96,7 @@ async fn build_admin_list_dimension_collectors_response(
|
||||
Err(detail) => return Ok(build_admin_billing_bad_request_response(detail)),
|
||||
};
|
||||
|
||||
if let Some((items, total)) = state
|
||||
let (items, total) = state
|
||||
.list_admin_billing_collectors(
|
||||
api_format.as_deref(),
|
||||
task_type.as_deref(),
|
||||
@@ -126,90 +106,13 @@ async fn build_admin_list_dimension_collectors_response(
|
||||
page_size,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
return Ok(Json(json!({
|
||||
"items": items
|
||||
.iter()
|
||||
.map(build_admin_billing_collector_payload_from_record)
|
||||
.collect::<Vec<_>>(),
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
"pages": admin_billing_pages(total, page_size),
|
||||
}))
|
||||
.into_response());
|
||||
}
|
||||
|
||||
let mut total = 0_u64;
|
||||
let mut items = Vec::new();
|
||||
if let Some(pool) = state.postgres_pool() {
|
||||
let count_row = sqlx::query(
|
||||
r#"
|
||||
SELECT COUNT(*) AS total
|
||||
FROM dimension_collectors
|
||||
WHERE ($1::TEXT IS NULL OR api_format = $1)
|
||||
AND ($2::TEXT IS NULL OR task_type = $2)
|
||||
AND ($3::TEXT IS NULL OR dimension_name = $3)
|
||||
AND ($4::BOOL IS NULL OR is_enabled = $4)
|
||||
"#,
|
||||
)
|
||||
.bind(api_format.as_deref())
|
||||
.bind(task_type.as_deref())
|
||||
.bind(dimension_name.as_deref())
|
||||
.bind(is_enabled)
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))?;
|
||||
total = count_row
|
||||
.try_get::<i64, _>("total")
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))?
|
||||
.max(0) as u64;
|
||||
|
||||
let offset = u64::from(page.saturating_sub(1) * page_size);
|
||||
let rows = sqlx::query(
|
||||
r#"
|
||||
SELECT
|
||||
id,
|
||||
api_format,
|
||||
task_type,
|
||||
dimension_name,
|
||||
source_type,
|
||||
source_path,
|
||||
value_type,
|
||||
transform_expression,
|
||||
default_value,
|
||||
priority,
|
||||
is_enabled,
|
||||
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
|
||||
FROM dimension_collectors
|
||||
WHERE ($1::TEXT IS NULL OR api_format = $1)
|
||||
AND ($2::TEXT IS NULL OR task_type = $2)
|
||||
AND ($3::TEXT IS NULL OR dimension_name = $3)
|
||||
AND ($4::BOOL IS NULL OR is_enabled = $4)
|
||||
ORDER BY updated_at DESC, priority DESC, id ASC
|
||||
OFFSET $5
|
||||
LIMIT $6
|
||||
"#,
|
||||
)
|
||||
.bind(api_format.as_deref())
|
||||
.bind(task_type.as_deref())
|
||||
.bind(dimension_name.as_deref())
|
||||
.bind(is_enabled)
|
||||
.bind(i64::try_from(offset).map_err(|err| GatewayError::Internal(err.to_string()))?)
|
||||
.bind(i64::from(page_size))
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))?;
|
||||
|
||||
items = rows
|
||||
.iter()
|
||||
.map(admin_billing_collector_payload)
|
||||
.collect::<Result<Vec<_>, GatewayError>>()?;
|
||||
}
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(Json(json!({
|
||||
"items": items,
|
||||
"items": items
|
||||
.iter()
|
||||
.map(build_admin_billing_collector_payload_from_record)
|
||||
.collect::<Vec<_>>(),
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
@@ -229,45 +132,10 @@ async fn build_admin_get_dimension_collector_response(
|
||||
));
|
||||
};
|
||||
|
||||
if let Some(record) = state.read_admin_billing_collector(&collector_id).await? {
|
||||
return Ok(
|
||||
Json(build_admin_billing_collector_payload_from_record(&record)).into_response(),
|
||||
);
|
||||
}
|
||||
|
||||
let Some(pool) = state.postgres_pool() else {
|
||||
return Ok(build_admin_billing_not_found_response(
|
||||
"Dimension collector not found",
|
||||
));
|
||||
};
|
||||
|
||||
let row = sqlx::query(
|
||||
r#"
|
||||
SELECT
|
||||
id,
|
||||
api_format,
|
||||
task_type,
|
||||
dimension_name,
|
||||
source_type,
|
||||
source_path,
|
||||
value_type,
|
||||
transform_expression,
|
||||
default_value,
|
||||
priority,
|
||||
is_enabled,
|
||||
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
|
||||
FROM dimension_collectors
|
||||
WHERE id = $1
|
||||
"#,
|
||||
)
|
||||
.bind(&collector_id)
|
||||
.fetch_optional(&pool)
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))?;
|
||||
|
||||
match row {
|
||||
Some(row) => Ok(Json(admin_billing_collector_payload(&row)?).into_response()),
|
||||
match state.read_admin_billing_collector(&collector_id).await? {
|
||||
Some(record) => {
|
||||
Ok(Json(build_admin_billing_collector_payload_from_record(&record)).into_response())
|
||||
}
|
||||
None => Ok(build_admin_billing_not_found_response(
|
||||
"Dimension collector not found",
|
||||
)),
|
||||
@@ -278,7 +146,7 @@ async fn parse_admin_billing_collector_request(
|
||||
state: &AppState,
|
||||
request_body: Option<&Bytes>,
|
||||
existing_id: Option<&str>,
|
||||
) -> Result<crate::gateway::AdminBillingCollectorWriteInput, Response<Body>> {
|
||||
) -> Result<crate::AdminBillingCollectorWriteInput, Response<Body>> {
|
||||
let Some(request_body) = request_body else {
|
||||
return Err(build_admin_billing_bad_request_response("请求体不能为空"));
|
||||
};
|
||||
@@ -391,7 +259,7 @@ async fn parse_admin_billing_collector_request(
|
||||
}
|
||||
}
|
||||
|
||||
Ok(crate::gateway::AdminBillingCollectorWriteInput {
|
||||
Ok(crate::AdminBillingCollectorWriteInput {
|
||||
api_format,
|
||||
task_type,
|
||||
dimension_name,
|
||||
@@ -414,16 +282,16 @@ async fn build_admin_create_dimension_collector_response(
|
||||
Err(response) => return Ok(response),
|
||||
};
|
||||
match state.create_admin_billing_collector(&input).await? {
|
||||
crate::gateway::LocalMutationOutcome::Applied(record) => {
|
||||
crate::LocalMutationOutcome::Applied(record) => {
|
||||
Ok(Json(build_admin_billing_collector_payload_from_record(&record)).into_response())
|
||||
}
|
||||
crate::gateway::LocalMutationOutcome::Invalid(detail) => {
|
||||
crate::LocalMutationOutcome::Invalid(detail) => {
|
||||
Ok(build_admin_billing_bad_request_response(detail))
|
||||
}
|
||||
crate::gateway::LocalMutationOutcome::NotFound => Ok(
|
||||
crate::LocalMutationOutcome::NotFound => Ok(
|
||||
build_admin_billing_not_found_response("Dimension collector not found"),
|
||||
),
|
||||
crate::gateway::LocalMutationOutcome::Unavailable => Ok(
|
||||
crate::LocalMutationOutcome::Unavailable => Ok(
|
||||
build_admin_billing_read_only_response("当前为只读模式,无法创建维度采集器"),
|
||||
),
|
||||
}
|
||||
@@ -450,16 +318,16 @@ async fn build_admin_update_dimension_collector_response(
|
||||
.update_admin_billing_collector(&collector_id, &input)
|
||||
.await?
|
||||
{
|
||||
crate::gateway::LocalMutationOutcome::Applied(record) => {
|
||||
crate::LocalMutationOutcome::Applied(record) => {
|
||||
Ok(Json(build_admin_billing_collector_payload_from_record(&record)).into_response())
|
||||
}
|
||||
crate::gateway::LocalMutationOutcome::NotFound => Ok(
|
||||
crate::LocalMutationOutcome::NotFound => Ok(
|
||||
build_admin_billing_not_found_response("Dimension collector not found"),
|
||||
),
|
||||
crate::gateway::LocalMutationOutcome::Invalid(detail) => {
|
||||
crate::LocalMutationOutcome::Invalid(detail) => {
|
||||
Ok(build_admin_billing_bad_request_response(detail))
|
||||
}
|
||||
crate::gateway::LocalMutationOutcome::Unavailable => Ok(
|
||||
crate::LocalMutationOutcome::Unavailable => Ok(
|
||||
build_admin_billing_read_only_response("当前为只读模式,无法更新维度采集器"),
|
||||
),
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ use super::{
|
||||
build_admin_billing_bad_request_response, build_admin_billing_not_found_response,
|
||||
build_admin_billing_read_only_response, normalize_admin_billing_required_text,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::admin::misc_helpers::attach_admin_audit_response;
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
response::{IntoResponse, Response},
|
||||
@@ -36,9 +38,9 @@ fn build_admin_billing_presets_payload() -> serde_json::Value {
|
||||
}
|
||||
|
||||
fn build_admin_billing_aether_core_collectors(
|
||||
) -> Vec<crate::gateway::AdminBillingCollectorWriteInput> {
|
||||
) -> Vec<crate::AdminBillingCollectorWriteInput> {
|
||||
vec![
|
||||
crate::gateway::AdminBillingCollectorWriteInput {
|
||||
crate::AdminBillingCollectorWriteInput {
|
||||
api_format: "OPENAI:CHAT".to_string(),
|
||||
task_type: "chat".to_string(),
|
||||
dimension_name: "input_tokens".to_string(),
|
||||
@@ -50,7 +52,7 @@ fn build_admin_billing_aether_core_collectors(
|
||||
priority: 10,
|
||||
is_enabled: true,
|
||||
},
|
||||
crate::gateway::AdminBillingCollectorWriteInput {
|
||||
crate::AdminBillingCollectorWriteInput {
|
||||
api_format: "OPENAI:CHAT".to_string(),
|
||||
task_type: "chat".to_string(),
|
||||
dimension_name: "output_tokens".to_string(),
|
||||
@@ -62,7 +64,7 @@ fn build_admin_billing_aether_core_collectors(
|
||||
priority: 10,
|
||||
is_enabled: true,
|
||||
},
|
||||
crate::gateway::AdminBillingCollectorWriteInput {
|
||||
crate::AdminBillingCollectorWriteInput {
|
||||
api_format: "CLAUDE:CHAT".to_string(),
|
||||
task_type: "chat".to_string(),
|
||||
dimension_name: "input_tokens".to_string(),
|
||||
@@ -74,7 +76,7 @@ fn build_admin_billing_aether_core_collectors(
|
||||
priority: 10,
|
||||
is_enabled: true,
|
||||
},
|
||||
crate::gateway::AdminBillingCollectorWriteInput {
|
||||
crate::AdminBillingCollectorWriteInput {
|
||||
api_format: "CLAUDE:CHAT".to_string(),
|
||||
task_type: "chat".to_string(),
|
||||
dimension_name: "output_tokens".to_string(),
|
||||
@@ -86,7 +88,7 @@ fn build_admin_billing_aether_core_collectors(
|
||||
priority: 10,
|
||||
is_enabled: true,
|
||||
},
|
||||
crate::gateway::AdminBillingCollectorWriteInput {
|
||||
crate::AdminBillingCollectorWriteInput {
|
||||
api_format: "GEMINI:CHAT".to_string(),
|
||||
task_type: "chat".to_string(),
|
||||
dimension_name: "input_tokens".to_string(),
|
||||
@@ -98,7 +100,7 @@ fn build_admin_billing_aether_core_collectors(
|
||||
priority: 10,
|
||||
is_enabled: true,
|
||||
},
|
||||
crate::gateway::AdminBillingCollectorWriteInput {
|
||||
crate::AdminBillingCollectorWriteInput {
|
||||
api_format: "GEMINI:CHAT".to_string(),
|
||||
task_type: "chat".to_string(),
|
||||
dimension_name: "output_tokens".to_string(),
|
||||
@@ -110,7 +112,7 @@ fn build_admin_billing_aether_core_collectors(
|
||||
priority: 10,
|
||||
is_enabled: true,
|
||||
},
|
||||
crate::gateway::AdminBillingCollectorWriteInput {
|
||||
crate::AdminBillingCollectorWriteInput {
|
||||
api_format: "OPENAI:CHAT".to_string(),
|
||||
task_type: "video".to_string(),
|
||||
dimension_name: "video_resolution_key".to_string(),
|
||||
@@ -122,7 +124,7 @@ fn build_admin_billing_aether_core_collectors(
|
||||
priority: 10,
|
||||
is_enabled: true,
|
||||
},
|
||||
crate::gateway::AdminBillingCollectorWriteInput {
|
||||
crate::AdminBillingCollectorWriteInput {
|
||||
api_format: "OPENAI:CHAT".to_string(),
|
||||
task_type: "video".to_string(),
|
||||
dimension_name: "video_resolution_key".to_string(),
|
||||
@@ -134,7 +136,7 @@ fn build_admin_billing_aether_core_collectors(
|
||||
priority: 0,
|
||||
is_enabled: true,
|
||||
},
|
||||
crate::gateway::AdminBillingCollectorWriteInput {
|
||||
crate::AdminBillingCollectorWriteInput {
|
||||
api_format: "OPENAI:CHAT".to_string(),
|
||||
task_type: "video".to_string(),
|
||||
dimension_name: "video_size_bytes".to_string(),
|
||||
@@ -146,7 +148,7 @@ fn build_admin_billing_aether_core_collectors(
|
||||
priority: 0,
|
||||
is_enabled: true,
|
||||
},
|
||||
crate::gateway::AdminBillingCollectorWriteInput {
|
||||
crate::AdminBillingCollectorWriteInput {
|
||||
api_format: "OPENAI:CHAT".to_string(),
|
||||
task_type: "video".to_string(),
|
||||
dimension_name: "video_duration_seconds".to_string(),
|
||||
@@ -158,7 +160,7 @@ fn build_admin_billing_aether_core_collectors(
|
||||
priority: 10,
|
||||
is_enabled: true,
|
||||
},
|
||||
crate::gateway::AdminBillingCollectorWriteInput {
|
||||
crate::AdminBillingCollectorWriteInput {
|
||||
api_format: "OPENAI:CHAT".to_string(),
|
||||
task_type: "video".to_string(),
|
||||
dimension_name: "video_duration_seconds".to_string(),
|
||||
@@ -170,7 +172,7 @@ fn build_admin_billing_aether_core_collectors(
|
||||
priority: 0,
|
||||
is_enabled: true,
|
||||
},
|
||||
crate::gateway::AdminBillingCollectorWriteInput {
|
||||
crate::AdminBillingCollectorWriteInput {
|
||||
api_format: "GEMINI:CHAT".to_string(),
|
||||
task_type: "video".to_string(),
|
||||
dimension_name: "video_resolution_key".to_string(),
|
||||
@@ -182,7 +184,7 @@ fn build_admin_billing_aether_core_collectors(
|
||||
priority: 10,
|
||||
is_enabled: true,
|
||||
},
|
||||
crate::gateway::AdminBillingCollectorWriteInput {
|
||||
crate::AdminBillingCollectorWriteInput {
|
||||
api_format: "GEMINI:CHAT".to_string(),
|
||||
task_type: "video".to_string(),
|
||||
dimension_name: "video_resolution_key".to_string(),
|
||||
@@ -194,7 +196,7 @@ fn build_admin_billing_aether_core_collectors(
|
||||
priority: 0,
|
||||
is_enabled: true,
|
||||
},
|
||||
crate::gateway::AdminBillingCollectorWriteInput {
|
||||
crate::AdminBillingCollectorWriteInput {
|
||||
api_format: "GEMINI:CHAT".to_string(),
|
||||
task_type: "video".to_string(),
|
||||
dimension_name: "video_size_bytes".to_string(),
|
||||
@@ -206,7 +208,7 @@ fn build_admin_billing_aether_core_collectors(
|
||||
priority: 0,
|
||||
is_enabled: true,
|
||||
},
|
||||
crate::gateway::AdminBillingCollectorWriteInput {
|
||||
crate::AdminBillingCollectorWriteInput {
|
||||
api_format: "GEMINI:CHAT".to_string(),
|
||||
task_type: "video".to_string(),
|
||||
dimension_name: "video_duration_seconds".to_string(),
|
||||
@@ -218,7 +220,7 @@ fn build_admin_billing_aether_core_collectors(
|
||||
priority: 10,
|
||||
is_enabled: true,
|
||||
},
|
||||
crate::gateway::AdminBillingCollectorWriteInput {
|
||||
crate::AdminBillingCollectorWriteInput {
|
||||
api_format: "GEMINI:CHAT".to_string(),
|
||||
task_type: "video".to_string(),
|
||||
dimension_name: "video_duration_seconds".to_string(),
|
||||
@@ -237,7 +239,7 @@ fn resolve_admin_billing_preset_collectors(
|
||||
preset: &str,
|
||||
) -> Option<(
|
||||
&'static str,
|
||||
Vec<crate::gateway::AdminBillingCollectorWriteInput>,
|
||||
Vec<crate::AdminBillingCollectorWriteInput>,
|
||||
)> {
|
||||
let normalized = preset.trim().to_ascii_lowercase();
|
||||
match normalized.as_str() {
|
||||
@@ -277,6 +279,7 @@ fn parse_admin_billing_preset_apply_request(
|
||||
|
||||
async fn build_admin_apply_billing_preset_response(
|
||||
state: &AppState,
|
||||
request_context: &GatewayPublicRequestContext,
|
||||
request_body: Option<&Bytes>,
|
||||
) -> Result<Response<Body>, GatewayError> {
|
||||
let (preset, mode) = match parse_admin_billing_preset_apply_request(request_body) {
|
||||
@@ -301,23 +304,32 @@ async fn build_admin_apply_billing_preset_response(
|
||||
.apply_admin_billing_preset(resolved_preset, &mode, &collectors)
|
||||
.await?
|
||||
{
|
||||
crate::gateway::LocalMutationOutcome::Applied(result) => Ok(Json(json!({
|
||||
"ok": result.errors.is_empty(),
|
||||
"preset": result.preset,
|
||||
"mode": result.mode,
|
||||
"created": result.created,
|
||||
"updated": result.updated,
|
||||
"skipped": result.skipped,
|
||||
"errors": result.errors,
|
||||
}))
|
||||
.into_response()),
|
||||
crate::gateway::LocalMutationOutcome::Unavailable => Ok(
|
||||
crate::LocalMutationOutcome::Applied(result) => {
|
||||
let response = Json(json!({
|
||||
"ok": result.errors.is_empty(),
|
||||
"preset": result.preset,
|
||||
"mode": result.mode,
|
||||
"created": result.created,
|
||||
"updated": result.updated,
|
||||
"skipped": result.skipped,
|
||||
"errors": result.errors,
|
||||
}))
|
||||
.into_response();
|
||||
Ok(attach_admin_audit_response(
|
||||
response,
|
||||
"admin_billing_preset_applied",
|
||||
"apply_billing_preset",
|
||||
"billing_preset",
|
||||
resolved_preset,
|
||||
))
|
||||
}
|
||||
crate::LocalMutationOutcome::Unavailable => Ok(
|
||||
build_admin_billing_read_only_response("当前为只读模式,无法应用计费预设"),
|
||||
),
|
||||
crate::gateway::LocalMutationOutcome::Invalid(detail) => {
|
||||
crate::LocalMutationOutcome::Invalid(detail) => {
|
||||
Ok(build_admin_billing_bad_request_response(detail))
|
||||
}
|
||||
crate::gateway::LocalMutationOutcome::NotFound => Ok(
|
||||
crate::LocalMutationOutcome::NotFound => Ok(
|
||||
build_admin_billing_not_found_response("Billing preset not found"),
|
||||
),
|
||||
}
|
||||
@@ -353,7 +365,8 @@ pub(super) async fn maybe_build_local_admin_billing_presets_response(
|
||||
) =>
|
||||
{
|
||||
Ok(Some(
|
||||
build_admin_apply_billing_preset_response(state, request_body).await?,
|
||||
build_admin_apply_billing_preset_response(state, request_context, request_body)
|
||||
.await?,
|
||||
))
|
||||
}
|
||||
_ => Ok(None),
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
use super::{
|
||||
admin_billing_optional_bool_filter, admin_billing_optional_epoch_value,
|
||||
admin_billing_optional_filter, admin_billing_pages, admin_billing_parse_page,
|
||||
admin_billing_parse_page_size, admin_billing_validate_safe_expression,
|
||||
build_admin_billing_bad_request_response, build_admin_billing_not_found_response,
|
||||
build_admin_billing_read_only_response, default_admin_billing_json_object,
|
||||
default_admin_billing_true, normalize_admin_billing_optional_text,
|
||||
normalize_admin_billing_required_text,
|
||||
admin_billing_optional_bool_filter, admin_billing_optional_filter, admin_billing_pages,
|
||||
admin_billing_parse_page, admin_billing_parse_page_size,
|
||||
admin_billing_validate_safe_expression, build_admin_billing_bad_request_response,
|
||||
build_admin_billing_not_found_response, build_admin_billing_read_only_response,
|
||||
default_admin_billing_json_object, default_admin_billing_true,
|
||||
normalize_admin_billing_optional_text, normalize_admin_billing_required_text,
|
||||
};
|
||||
use crate::gateway::handlers::unix_secs_to_rfc3339;
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::unix_secs_to_rfc3339;
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
http,
|
||||
@@ -17,7 +17,6 @@ use axum::{
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use sqlx::Row;
|
||||
|
||||
fn default_admin_billing_rule_task_type() -> String {
|
||||
"chat".to_string()
|
||||
@@ -42,7 +41,7 @@ struct AdminBillingRuleUpsertRequest {
|
||||
}
|
||||
|
||||
fn build_admin_billing_rule_payload_from_record(
|
||||
record: &crate::gateway::AdminBillingRuleRecord,
|
||||
record: &crate::AdminBillingRuleRecord,
|
||||
) -> serde_json::Value {
|
||||
json!({
|
||||
"id": record.id,
|
||||
@@ -74,7 +73,7 @@ fn admin_billing_rule_id_from_path(request_path: &str) -> Option<String> {
|
||||
|
||||
fn parse_admin_billing_rule_request(
|
||||
request_body: Option<&Bytes>,
|
||||
) -> Result<crate::gateway::AdminBillingRuleWriteInput, Response<Body>> {
|
||||
) -> Result<crate::AdminBillingRuleWriteInput, Response<Body>> {
|
||||
let Some(request_body) = request_body else {
|
||||
return Err(build_admin_billing_bad_request_response("请求体不能为空"));
|
||||
};
|
||||
@@ -158,7 +157,7 @@ fn parse_admin_billing_rule_request(
|
||||
}
|
||||
}
|
||||
|
||||
Ok(crate::gateway::AdminBillingRuleWriteInput {
|
||||
Ok(crate::AdminBillingRuleWriteInput {
|
||||
name,
|
||||
task_type,
|
||||
global_model_id,
|
||||
@@ -170,24 +169,6 @@ fn parse_admin_billing_rule_request(
|
||||
})
|
||||
}
|
||||
|
||||
fn admin_billing_rule_payload(
|
||||
row: &sqlx::postgres::PgRow,
|
||||
) -> Result<serde_json::Value, GatewayError> {
|
||||
Ok(json!({
|
||||
"id": row.try_get::<String, _>("id").map_err(|err| GatewayError::Internal(err.to_string()))?,
|
||||
"name": row.try_get::<String, _>("name").map_err(|err| GatewayError::Internal(err.to_string()))?,
|
||||
"task_type": row.try_get::<String, _>("task_type").map_err(|err| GatewayError::Internal(err.to_string()))?,
|
||||
"global_model_id": row.try_get::<Option<String>, _>("global_model_id").map_err(|err| GatewayError::Internal(err.to_string()))?,
|
||||
"model_id": row.try_get::<Option<String>, _>("model_id").map_err(|err| GatewayError::Internal(err.to_string()))?,
|
||||
"expression": row.try_get::<String, _>("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(|| json!({})),
|
||||
"dimension_mappings": row.try_get::<Option<serde_json::Value>, _>("dimension_mappings").map_err(|err| GatewayError::Internal(err.to_string()))?.unwrap_or_else(|| json!({})),
|
||||
"is_enabled": row.try_get::<bool, _>("is_enabled").map_err(|err| GatewayError::Internal(err.to_string()))?,
|
||||
"created_at": admin_billing_optional_epoch_value(row, "created_at_unix_secs")?,
|
||||
"updated_at": admin_billing_optional_epoch_value(row, "updated_at_unix_secs")?,
|
||||
}))
|
||||
}
|
||||
|
||||
async fn build_admin_list_billing_rules_response(
|
||||
state: &AppState,
|
||||
request_context: &GatewayPublicRequestContext,
|
||||
@@ -207,72 +188,20 @@ async fn build_admin_list_billing_rules_response(
|
||||
Err(detail) => return Ok(build_admin_billing_bad_request_response(detail)),
|
||||
};
|
||||
|
||||
let mut total = 0_u64;
|
||||
let mut items = Vec::new();
|
||||
if let Some((records, record_total)) = state
|
||||
let (items, total) = if let Some((records, record_total)) = state
|
||||
.list_admin_billing_rules(task_type.as_deref(), is_enabled, page, page_size)
|
||||
.await?
|
||||
{
|
||||
total = record_total;
|
||||
items = records
|
||||
.iter()
|
||||
.map(build_admin_billing_rule_payload_from_record)
|
||||
.collect::<Vec<_>>();
|
||||
} else if let Some(pool) = state.postgres_pool() {
|
||||
let count_row = sqlx::query(
|
||||
r#"
|
||||
SELECT COUNT(*) AS total
|
||||
FROM billing_rules
|
||||
WHERE ($1::TEXT IS NULL OR task_type = $1)
|
||||
AND ($2::BOOL IS NULL OR is_enabled = $2)
|
||||
"#,
|
||||
(
|
||||
records
|
||||
.iter()
|
||||
.map(build_admin_billing_rule_payload_from_record)
|
||||
.collect::<Vec<_>>(),
|
||||
record_total,
|
||||
)
|
||||
.bind(task_type.as_deref())
|
||||
.bind(is_enabled)
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))?;
|
||||
total = count_row
|
||||
.try_get::<i64, _>("total")
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))?
|
||||
.max(0) as u64;
|
||||
|
||||
let offset = u64::from(page.saturating_sub(1) * page_size);
|
||||
let rows = sqlx::query(
|
||||
r#"
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
task_type,
|
||||
global_model_id,
|
||||
model_id,
|
||||
expression,
|
||||
variables,
|
||||
dimension_mappings,
|
||||
is_enabled,
|
||||
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
|
||||
FROM billing_rules
|
||||
WHERE ($1::TEXT IS NULL OR task_type = $1)
|
||||
AND ($2::BOOL IS NULL OR is_enabled = $2)
|
||||
ORDER BY updated_at DESC
|
||||
OFFSET $3
|
||||
LIMIT $4
|
||||
"#,
|
||||
)
|
||||
.bind(task_type.as_deref())
|
||||
.bind(is_enabled)
|
||||
.bind(i64::try_from(offset).map_err(|err| GatewayError::Internal(err.to_string()))?)
|
||||
.bind(i64::from(page_size))
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))?;
|
||||
|
||||
items = rows
|
||||
.iter()
|
||||
.map(admin_billing_rule_payload)
|
||||
.collect::<Result<Vec<_>, GatewayError>>()?;
|
||||
}
|
||||
} else {
|
||||
(Vec::new(), 0)
|
||||
};
|
||||
|
||||
Ok(Json(json!({
|
||||
"items": items,
|
||||
@@ -291,40 +220,10 @@ async fn build_admin_get_billing_rule_response(
|
||||
let Some(rule_id) = admin_billing_rule_id_from_path(&request_context.request_path) else {
|
||||
return Ok(build_admin_billing_bad_request_response("缺少 rule_id"));
|
||||
};
|
||||
if let Some(record) = state.read_admin_billing_rule(&rule_id).await? {
|
||||
return Ok(Json(build_admin_billing_rule_payload_from_record(&record)).into_response());
|
||||
}
|
||||
let Some(pool) = state.postgres_pool() else {
|
||||
return Ok(build_admin_billing_not_found_response(
|
||||
"Billing rule not found",
|
||||
));
|
||||
};
|
||||
|
||||
let row = sqlx::query(
|
||||
r#"
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
task_type,
|
||||
global_model_id,
|
||||
model_id,
|
||||
expression,
|
||||
variables,
|
||||
dimension_mappings,
|
||||
is_enabled,
|
||||
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
|
||||
FROM billing_rules
|
||||
WHERE id = $1
|
||||
"#,
|
||||
)
|
||||
.bind(&rule_id)
|
||||
.fetch_optional(&pool)
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))?;
|
||||
|
||||
match row {
|
||||
Some(row) => Ok(Json(admin_billing_rule_payload(&row)?).into_response()),
|
||||
match state.read_admin_billing_rule(&rule_id).await? {
|
||||
Some(record) => {
|
||||
Ok(Json(build_admin_billing_rule_payload_from_record(&record)).into_response())
|
||||
}
|
||||
None => Ok(build_admin_billing_not_found_response(
|
||||
"Billing rule not found",
|
||||
)),
|
||||
@@ -340,16 +239,16 @@ async fn build_admin_create_billing_rule_response(
|
||||
Err(response) => return Ok(response),
|
||||
};
|
||||
match state.create_admin_billing_rule(&input).await? {
|
||||
crate::gateway::LocalMutationOutcome::Applied(record) => {
|
||||
crate::LocalMutationOutcome::Applied(record) => {
|
||||
Ok(Json(build_admin_billing_rule_payload_from_record(&record)).into_response())
|
||||
}
|
||||
crate::gateway::LocalMutationOutcome::Invalid(detail) => {
|
||||
crate::LocalMutationOutcome::Invalid(detail) => {
|
||||
Ok(build_admin_billing_bad_request_response(detail))
|
||||
}
|
||||
crate::gateway::LocalMutationOutcome::NotFound => Ok(
|
||||
crate::LocalMutationOutcome::NotFound => Ok(
|
||||
build_admin_billing_not_found_response("Billing rule not found"),
|
||||
),
|
||||
crate::gateway::LocalMutationOutcome::Unavailable => Ok(
|
||||
crate::LocalMutationOutcome::Unavailable => Ok(
|
||||
build_admin_billing_read_only_response("当前为只读模式,无法创建计费规则"),
|
||||
),
|
||||
}
|
||||
@@ -368,16 +267,16 @@ async fn build_admin_update_billing_rule_response(
|
||||
Err(response) => return Ok(response),
|
||||
};
|
||||
match state.update_admin_billing_rule(&rule_id, &input).await? {
|
||||
crate::gateway::LocalMutationOutcome::Applied(record) => {
|
||||
crate::LocalMutationOutcome::Applied(record) => {
|
||||
Ok(Json(build_admin_billing_rule_payload_from_record(&record)).into_response())
|
||||
}
|
||||
crate::gateway::LocalMutationOutcome::NotFound => Ok(
|
||||
crate::LocalMutationOutcome::NotFound => Ok(
|
||||
build_admin_billing_not_found_response("Billing rule not found"),
|
||||
),
|
||||
crate::gateway::LocalMutationOutcome::Invalid(detail) => {
|
||||
crate::LocalMutationOutcome::Invalid(detail) => {
|
||||
Ok(build_admin_billing_bad_request_response(detail))
|
||||
}
|
||||
crate::gateway::LocalMutationOutcome::Unavailable => Ok(
|
||||
crate::LocalMutationOutcome::Unavailable => Ok(
|
||||
build_admin_billing_read_only_response("当前为只读模式,无法更新计费规则"),
|
||||
),
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use super::{
|
||||
normalize_auth_type, normalize_json_object, normalize_string_list, validate_vertex_api_formats,
|
||||
};
|
||||
use crate::gateway::handlers::{
|
||||
use crate::handlers::{
|
||||
build_admin_provider_key_response, decrypt_catalog_secret_with_fallbacks,
|
||||
encrypt_catalog_secret_with_fallbacks, json_string_list, parse_catalog_auth_config_json,
|
||||
AdminProviderKeyCreateRequest, AdminProviderKeyUpdateRequest,
|
||||
};
|
||||
use crate::gateway::AppState;
|
||||
use crate::AppState;
|
||||
use aether_data::repository::provider_catalog::{
|
||||
StoredProviderCatalogKey, StoredProviderCatalogProvider,
|
||||
};
|
||||
|
||||
@@ -2,13 +2,13 @@ use super::{
|
||||
normalize_json_object, normalize_provider_billing_type, normalize_provider_type_input,
|
||||
parse_optional_rfc3339_unix_secs,
|
||||
};
|
||||
use crate::gateway::api::ai::{
|
||||
use crate::api::ai::{
|
||||
admin_default_body_rules_for_signature, admin_endpoint_signature_parts,
|
||||
};
|
||||
use crate::gateway::handlers::public::normalize_admin_base_url;
|
||||
use crate::gateway::handlers::{AdminProviderCreateRequest, AdminProviderUpdateRequest};
|
||||
use crate::gateway::provider_transport::provider_type_enables_format_conversion_by_default;
|
||||
use crate::gateway::AppState;
|
||||
use crate::handlers::public::normalize_admin_base_url;
|
||||
use crate::handlers::{AdminProviderCreateRequest, AdminProviderUpdateRequest};
|
||||
use crate::provider_transport::provider_types::provider_type_enables_format_conversion_by_default;
|
||||
use crate::AppState;
|
||||
use aether_data::repository::provider_catalog::{
|
||||
StoredProviderCatalogEndpoint, StoredProviderCatalogProvider,
|
||||
};
|
||||
|
||||
@@ -6,10 +6,10 @@ fn normalize_reveal_auth_type(value: &str) -> &str {
|
||||
}
|
||||
}
|
||||
|
||||
use crate::gateway::handlers::{
|
||||
use crate::handlers::{
|
||||
decrypt_catalog_secret_with_fallbacks, parse_catalog_auth_config_json,
|
||||
};
|
||||
use crate::gateway::AppState;
|
||||
use crate::AppState;
|
||||
use aether_data::repository::provider_catalog::StoredProviderCatalogKey;
|
||||
use chrono::{SecondsFormat, Utc};
|
||||
use serde_json::json;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
http,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use crate::gateway::handlers::internal::build_management_token_payload;
|
||||
use crate::gateway::handlers::{
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::internal::build_management_token_payload;
|
||||
use crate::handlers::{
|
||||
admin_management_token_id_from_path, admin_management_token_status_id_from_path,
|
||||
is_admin_management_tokens_root, query_param_optional_bool, query_param_value,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::{AppState, GatewayError};
|
||||
use aether_data::repository::management_tokens::ManagementTokenListQuery;
|
||||
use axum::{
|
||||
body::Body,
|
||||
|
||||
@@ -3,7 +3,8 @@ use super::super::{
|
||||
read_admin_external_models_cache,
|
||||
};
|
||||
use super::build_admin_model_catalog_data_unavailable_response;
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::Body,
|
||||
http,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use crate::gateway::handlers::public::{
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::public::{
|
||||
admin_module_by_name, admin_module_name_from_enabled_path, admin_module_name_from_status_path,
|
||||
build_admin_module_runtime_state, build_admin_module_status_payload,
|
||||
build_admin_module_validation_result, build_admin_modules_status_payload,
|
||||
module_available_from_env, AdminSetModuleEnabledRequest,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
http,
|
||||
|
||||
@@ -2,11 +2,13 @@ use super::super::{
|
||||
build_admin_oauth_provider_payload, build_admin_oauth_supported_types_payload,
|
||||
build_admin_oauth_upsert_record, build_proxy_error_response,
|
||||
};
|
||||
use crate::gateway::handlers::{
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::admin::misc_helpers::attach_admin_audit_response;
|
||||
use crate::handlers::{
|
||||
admin_oauth_provider_type_from_path, admin_oauth_test_provider_type_from_path,
|
||||
AdminOAuthProviderUpsertRequest,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
http,
|
||||
@@ -44,7 +46,7 @@ pub(super) async fn maybe_build_local_admin_core_oauth_response(
|
||||
)
|
||||
{
|
||||
let providers = state.list_oauth_provider_configs().await?;
|
||||
return Ok(Some(
|
||||
return Ok(Some(attach_admin_audit_response(
|
||||
Json(
|
||||
providers
|
||||
.iter()
|
||||
@@ -52,7 +54,11 @@ pub(super) async fn maybe_build_local_admin_core_oauth_response(
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.into_response(),
|
||||
));
|
||||
"admin_oauth_provider_configs_viewed",
|
||||
"list_oauth_provider_configs",
|
||||
"oauth_provider",
|
||||
"all",
|
||||
)));
|
||||
}
|
||||
|
||||
if decision.route_kind.as_deref() == Some("get_provider")
|
||||
@@ -71,9 +77,13 @@ pub(super) async fn maybe_build_local_admin_core_oauth_response(
|
||||
};
|
||||
return Ok(Some(
|
||||
match state.get_oauth_provider_config(&provider_type).await? {
|
||||
Some(provider) => {
|
||||
Json(build_admin_oauth_provider_payload(&provider)).into_response()
|
||||
}
|
||||
Some(provider) => attach_admin_audit_response(
|
||||
Json(build_admin_oauth_provider_payload(&provider)).into_response(),
|
||||
"admin_oauth_provider_config_viewed",
|
||||
"view_oauth_provider_config",
|
||||
"oauth_provider",
|
||||
&provider_type,
|
||||
),
|
||||
None => (
|
||||
http::StatusCode::NOT_FOUND,
|
||||
Json(json!({ "detail": "Provider 配置不存在" })),
|
||||
@@ -299,7 +309,7 @@ pub(super) async fn maybe_build_local_admin_core_oauth_response(
|
||||
} else {
|
||||
"provider 未安装/不可用"
|
||||
};
|
||||
return Ok(Some(
|
||||
return Ok(Some(attach_admin_audit_response(
|
||||
Json(json!({
|
||||
"authorization_url_reachable": false,
|
||||
"token_url_reachable": false,
|
||||
@@ -307,7 +317,11 @@ pub(super) async fn maybe_build_local_admin_core_oauth_response(
|
||||
"details": details,
|
||||
}))
|
||||
.into_response(),
|
||||
));
|
||||
"admin_oauth_provider_tested",
|
||||
"test_oauth_provider_config",
|
||||
"oauth_provider",
|
||||
&provider_type,
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use super::super::build_proxy_error_response;
|
||||
use super::ADMIN_AWS_REGIONS;
|
||||
use crate::gateway::handlers::public::{
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::admin::misc_helpers::attach_admin_audit_response;
|
||||
use crate::handlers::public::{
|
||||
apply_admin_email_template_update, apply_admin_system_config_update,
|
||||
apply_admin_system_settings_update, build_admin_api_formats_payload,
|
||||
build_admin_email_template_payload, build_admin_email_templates_payload,
|
||||
@@ -10,12 +12,12 @@ use crate::gateway::handlers::public::{
|
||||
build_admin_system_users_export_payload, current_aether_version, delete_admin_system_config,
|
||||
preview_admin_email_template, reset_admin_email_template,
|
||||
};
|
||||
use crate::gateway::handlers::{
|
||||
use crate::handlers::{
|
||||
admin_system_config_key_from_path, admin_system_email_template_preview_type_from_path,
|
||||
admin_system_email_template_reset_type_from_path, admin_system_email_template_type_from_path,
|
||||
is_admin_system_configs_root, is_admin_system_email_templates_root,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
http,
|
||||
@@ -85,18 +87,26 @@ pub(super) async fn maybe_build_local_admin_core_system_response(
|
||||
&& request_context.request_method == http::Method::GET
|
||||
&& request_context.request_path == "/api/admin/system/config/export"
|
||||
{
|
||||
return Ok(Some(
|
||||
return Ok(Some(attach_admin_audit_response(
|
||||
Json(build_admin_system_config_export_payload(state).await?).into_response(),
|
||||
));
|
||||
"admin_system_config_exported",
|
||||
"export_system_config",
|
||||
"system_config_export",
|
||||
"global",
|
||||
)));
|
||||
}
|
||||
|
||||
if decision.route_kind.as_deref() == Some("users_export")
|
||||
&& request_context.request_method == http::Method::GET
|
||||
&& request_context.request_path == "/api/admin/system/users/export"
|
||||
{
|
||||
return Ok(Some(
|
||||
return Ok(Some(attach_admin_audit_response(
|
||||
Json(build_admin_system_users_export_payload(state).await?).into_response(),
|
||||
));
|
||||
"admin_system_users_exported",
|
||||
"export_system_users",
|
||||
"user_export",
|
||||
"all_users",
|
||||
)));
|
||||
}
|
||||
|
||||
if matches!(
|
||||
@@ -139,7 +149,13 @@ pub(super) async fn maybe_build_local_admin_core_system_response(
|
||||
};
|
||||
return Ok(Some(
|
||||
match apply_admin_system_settings_update(state, request_body).await? {
|
||||
Ok(payload) => Json(payload).into_response(),
|
||||
Ok(payload) => attach_admin_audit_response(
|
||||
Json(payload).into_response(),
|
||||
"admin_system_settings_updated",
|
||||
"update_system_settings",
|
||||
"system_settings",
|
||||
"global",
|
||||
),
|
||||
Err((status, payload)) => (status, Json(payload)).into_response(),
|
||||
},
|
||||
));
|
||||
@@ -197,7 +213,13 @@ pub(super) async fn maybe_build_local_admin_core_system_response(
|
||||
};
|
||||
return Ok(Some(
|
||||
match apply_admin_system_config_update(state, &config_key, request_body).await? {
|
||||
Ok(payload) => Json(payload).into_response(),
|
||||
Ok(payload) => attach_admin_audit_response(
|
||||
Json(payload).into_response(),
|
||||
"admin_system_config_updated",
|
||||
"update_system_config",
|
||||
"system_config",
|
||||
&config_key,
|
||||
),
|
||||
Err((status, payload)) => (status, Json(payload)).into_response(),
|
||||
},
|
||||
));
|
||||
@@ -217,7 +239,13 @@ pub(super) async fn maybe_build_local_admin_core_system_response(
|
||||
};
|
||||
return Ok(Some(
|
||||
match delete_admin_system_config(state, &config_key).await? {
|
||||
Ok(payload) => Json(payload).into_response(),
|
||||
Ok(payload) => attach_admin_audit_response(
|
||||
Json(payload).into_response(),
|
||||
"admin_system_config_deleted",
|
||||
"delete_system_config",
|
||||
"system_config",
|
||||
&config_key,
|
||||
),
|
||||
Err((status, payload)) => (status, Json(payload)).into_response(),
|
||||
},
|
||||
));
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::body::{Body, Bytes};
|
||||
use axum::http::Response;
|
||||
|
||||
|
||||
@@ -3,11 +3,12 @@ use super::super::{
|
||||
build_admin_health_summary_payload, build_admin_key_health_payload, recover_admin_key_health,
|
||||
recover_all_admin_key_health,
|
||||
};
|
||||
use crate::gateway::handlers::public::{
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::public::{
|
||||
build_api_format_health_monitor_payload, ApiFormatHealthMonitorOptions,
|
||||
};
|
||||
use crate::gateway::handlers::query_param_value;
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::handlers::query_param_value;
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::Body,
|
||||
http,
|
||||
|
||||
@@ -7,13 +7,15 @@ use super::super::{
|
||||
refresh_antigravity_provider_quota_locally, refresh_codex_provider_quota_locally,
|
||||
refresh_kiro_provider_quota_locally,
|
||||
};
|
||||
use crate::gateway::handlers::public::build_admin_keys_grouped_by_format_payload;
|
||||
use crate::gateway::handlers::{
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::admin::misc_helpers::attach_admin_audit_response;
|
||||
use crate::handlers::public::build_admin_keys_grouped_by_format_payload;
|
||||
use crate::handlers::{
|
||||
admin_provider_id_for_keys, query_param_value, AdminProviderKeyBatchDeleteRequest,
|
||||
AdminProviderKeyCreateRequest, AdminProviderKeyUpdateRequest, AdminProviderQuotaRefreshRequest,
|
||||
OAUTH_ACCOUNT_BLOCK_PREFIX,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
http,
|
||||
@@ -74,7 +76,13 @@ pub(super) async fn maybe_build_local_admin_endpoints_keys_response(
|
||||
));
|
||||
};
|
||||
return Ok(Some(match build_admin_reveal_key_payload(state, &key) {
|
||||
Ok(payload) => Json(payload).into_response(),
|
||||
Ok(payload) => attach_admin_audit_response(
|
||||
Json(payload).into_response(),
|
||||
"admin_provider_key_revealed",
|
||||
"reveal_provider_key",
|
||||
"provider_key",
|
||||
&key_id,
|
||||
),
|
||||
Err(detail) => (
|
||||
http::StatusCode::BAD_REQUEST,
|
||||
Json(json!({ "detail": detail })),
|
||||
@@ -115,7 +123,13 @@ pub(super) async fn maybe_build_local_admin_endpoints_keys_response(
|
||||
};
|
||||
return Ok(Some(
|
||||
match build_admin_export_key_payload(state, &key).await {
|
||||
Ok(payload) => Json(payload).into_response(),
|
||||
Ok(payload) => attach_admin_audit_response(
|
||||
Json(payload).into_response(),
|
||||
"admin_provider_key_exported",
|
||||
"export_provider_key",
|
||||
"provider_key_export",
|
||||
&key_id,
|
||||
),
|
||||
Err(detail) => (
|
||||
http::StatusCode::BAD_REQUEST,
|
||||
Json(json!({ "detail": detail })),
|
||||
|
||||
@@ -5,11 +5,12 @@ use super::super::{
|
||||
build_admin_update_provider_endpoint_record, endpoint_key_counts_by_format,
|
||||
key_api_formats_without_entry,
|
||||
};
|
||||
use crate::gateway::api::ai::admin_default_body_rules_for_signature;
|
||||
use crate::gateway::handlers::{
|
||||
use crate::api::ai::admin_default_body_rules_for_signature;
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::{
|
||||
query_param_value, AdminProviderEndpointCreateRequest, AdminProviderEndpointUpdateRequest,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
http,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::super::{admin_rpm_key_id, build_admin_key_rpm_payload};
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::Body,
|
||||
http,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::gateway::api::ai::{
|
||||
use crate::api::ai::{
|
||||
admin_default_body_rules_for_signature, admin_endpoint_signature_parts,
|
||||
};
|
||||
use crate::gateway::handlers::public::{admin_requested_force_stream, normalize_admin_base_url};
|
||||
use crate::gateway::provider_transport::provider_type_is_fixed;
|
||||
use crate::gateway::AppState;
|
||||
use crate::handlers::public::{admin_requested_force_stream, normalize_admin_base_url};
|
||||
use crate::provider_transport::provider_types::provider_type_is_fixed;
|
||||
use crate::AppState;
|
||||
use aether_data::repository::provider_catalog::{
|
||||
StoredProviderCatalogEndpoint, StoredProviderCatalogProvider,
|
||||
};
|
||||
@@ -12,7 +12,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::super::{build_admin_provider_endpoint_response, endpoint_key_counts_by_format};
|
||||
use crate::gateway::handlers::{
|
||||
use crate::handlers::{
|
||||
AdminProviderEndpointCreateRequest, AdminProviderEndpointUpdateRequest,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::gateway::handlers::public::provider_key_api_formats;
|
||||
use crate::gateway::scheduler::count_recent_rpm_requests_for_provider_key_since;
|
||||
use crate::gateway::AppState;
|
||||
use crate::handlers::public::provider_key_api_formats;
|
||||
use crate::scheduler::count_recent_rpm_requests_for_provider_key_since;
|
||||
use crate::AppState;
|
||||
use serde_json::json;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::gateway::handlers::public::{
|
||||
use crate::handlers::public::{
|
||||
api_format_display_name, build_public_health_timeline, provider_key_api_formats,
|
||||
};
|
||||
use crate::gateway::handlers::unix_secs_to_rfc3339;
|
||||
use crate::gateway::scheduler::{is_provider_key_circuit_open, provider_key_health_score};
|
||||
use crate::gateway::AppState;
|
||||
use crate::handlers::unix_secs_to_rfc3339;
|
||||
use crate::scheduler::{is_provider_key_circuit_open, provider_key_health_score};
|
||||
use crate::AppState;
|
||||
use aether_data::repository::candidates::PublicHealthTimelineBucket;
|
||||
use serde_json::json;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::{AppState, GatewayError};
|
||||
use aether_data::repository::provider_catalog::StoredProviderCatalogKey;
|
||||
use axum::body::{Body, Bytes};
|
||||
use axum::http::{self, Response};
|
||||
|
||||
@@ -4,12 +4,13 @@ use super::{
|
||||
ADMIN_GEMINI_FILES_DEFAULT_PAGE, ADMIN_GEMINI_FILES_DEFAULT_PAGE_SIZE,
|
||||
ADMIN_GEMINI_FILES_MAX_PAGE_SIZE,
|
||||
};
|
||||
use crate::gateway::handlers::{
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::{
|
||||
admin_gemini_file_mapping_id_from_path, is_admin_gemini_files_capable_keys_root,
|
||||
is_admin_gemini_files_mappings_root, is_admin_gemini_files_stats_root,
|
||||
query_param_optional_bool, query_param_value, unix_secs_to_rfc3339,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::body::Body;
|
||||
use axum::http::{self, Response};
|
||||
use axum::response::IntoResponse;
|
||||
|
||||
@@ -2,8 +2,9 @@ use super::{
|
||||
admin_gemini_files_error_response, admin_gemini_files_key_capable,
|
||||
ADMIN_GEMINI_FILES_DATA_UNAVAILABLE_DETAIL,
|
||||
};
|
||||
use crate::gateway::handlers::{is_admin_gemini_files_upload_root, query_param_value};
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::{is_admin_gemini_files_upload_root, query_param_value};
|
||||
use crate::{AppState, GatewayError};
|
||||
use aether_contracts::{ExecutionPlan, ExecutionResult, RequestBody};
|
||||
use aether_data::repository::provider_catalog::{
|
||||
StoredProviderCatalogEndpoint, StoredProviderCatalogKey,
|
||||
@@ -368,7 +369,7 @@ async fn admin_gemini_files_upload_single_key(
|
||||
.await
|
||||
.map_err(|err| format!("{err:?}"))?
|
||||
.ok_or_else(|| "无法读取 Key 传输配置".to_string())?;
|
||||
if !crate::gateway::provider_transport::supports_local_gemini_transport_with_network(
|
||||
if !crate::provider_transport::policy::supports_local_gemini_transport_with_network(
|
||||
&transport,
|
||||
"gemini:chat",
|
||||
) {
|
||||
@@ -378,11 +379,11 @@ async fn admin_gemini_files_upload_single_key(
|
||||
return Err("Gemini Files 二进制上传暂不支持 endpoint body_rules".to_string());
|
||||
}
|
||||
let (auth_header, auth_value) =
|
||||
crate::gateway::provider_transport::resolve_local_gemini_auth(&transport)
|
||||
crate::provider_transport::auth::resolve_local_gemini_auth(&transport)
|
||||
.ok_or_else(|| "Key 缺少可用的 Gemini 认证信息".to_string())?;
|
||||
|
||||
let mut provider_request_headers =
|
||||
crate::gateway::provider_transport::build_passthrough_headers_with_auth(
|
||||
crate::provider_transport::auth::build_passthrough_headers_with_auth(
|
||||
&http::HeaderMap::new(),
|
||||
&auth_header,
|
||||
&auth_value,
|
||||
@@ -392,7 +393,7 @@ async fn admin_gemini_files_upload_single_key(
|
||||
let original_request_body = json!({
|
||||
"body_bytes_b64": upload.body_bytes_b64,
|
||||
});
|
||||
if !crate::gateway::provider_transport::apply_local_header_rules(
|
||||
if !crate::provider_transport::apply_local_header_rules(
|
||||
&mut provider_request_headers,
|
||||
transport.endpoint.header_rules.as_ref(),
|
||||
&[auth_header.as_str(), "content-type"],
|
||||
@@ -414,7 +415,7 @@ async fn admin_gemini_files_upload_single_key(
|
||||
} else {
|
||||
Some("uploadType=resumable")
|
||||
};
|
||||
let upstream_url = crate::gateway::provider_transport::build_gemini_files_passthrough_url(
|
||||
let upstream_url = crate::provider_transport::url::build_gemini_files_passthrough_url(
|
||||
&transport.endpoint.base_url,
|
||||
upload_path,
|
||||
upload_query,
|
||||
@@ -442,9 +443,9 @@ async fn admin_gemini_files_upload_single_key(
|
||||
client_api_format: "gemini:files".to_string(),
|
||||
provider_api_format: "gemini:files".to_string(),
|
||||
model_name: Some("gemini-files".to_string()),
|
||||
proxy: crate::gateway::provider_transport::resolve_transport_proxy_snapshot_with_tunnel_affinity(state, &transport).await,
|
||||
tls_profile: crate::gateway::provider_transport::resolve_transport_tls_profile(&transport),
|
||||
timeouts: crate::gateway::provider_transport::resolve_transport_execution_timeouts(
|
||||
proxy: crate::provider_transport::resolve_transport_proxy_snapshot_with_tunnel_affinity(state, &transport).await,
|
||||
tls_profile: crate::provider_transport::resolve_transport_tls_profile(&transport),
|
||||
timeouts: crate::provider_transport::resolve_transport_execution_timeouts(
|
||||
&transport,
|
||||
),
|
||||
};
|
||||
@@ -459,7 +460,7 @@ async fn admin_gemini_files_upload_single_key(
|
||||
.ok_or_else(|| "上传成功但上游响应缺少 JSON body".to_string())?;
|
||||
let success = admin_gemini_files_upload_success_from_body(&body_json, upload)
|
||||
.ok_or_else(|| admin_gemini_files_execution_error_message(&result))?;
|
||||
crate::gateway::usage::store_local_gemini_file_mapping(
|
||||
crate::usage::reporting::store_local_gemini_file_mapping(
|
||||
state,
|
||||
success.file_name.as_str(),
|
||||
key.id.as_str(),
|
||||
@@ -483,7 +484,12 @@ async fn admin_gemini_files_execute_upload_plan(
|
||||
trace_id: &str,
|
||||
plan: &ExecutionPlan,
|
||||
) -> Result<ExecutionResult, GatewayError> {
|
||||
crate::gateway::execute_execution_runtime_sync_plan(state, Some(trace_id), plan).await
|
||||
crate::execution_runtime::execute_execution_runtime_sync_plan(
|
||||
state,
|
||||
Some(trace_id),
|
||||
plan,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn admin_gemini_files_execution_json_body(result: &ExecutionResult) -> Option<serde_json::Value> {
|
||||
|
||||
@@ -9,13 +9,15 @@ use super::global_models_helpers::{
|
||||
build_admin_global_models_data_unavailable_response,
|
||||
ADMIN_GLOBAL_MODELS_DATA_UNAVAILABLE_DETAIL,
|
||||
};
|
||||
use crate::gateway::handlers::{
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::admin::misc_helpers::attach_admin_audit_response;
|
||||
use crate::handlers::{
|
||||
admin_global_model_assign_to_providers_id, admin_global_model_id_from_path,
|
||||
admin_global_model_providers_id, admin_global_model_routing_id, is_admin_global_models_root,
|
||||
query_param_optional_bool, query_param_value, AdminBatchAssignToProvidersRequest,
|
||||
AdminBatchDeleteIdsRequest, AdminGlobalModelCreateRequest, AdminGlobalModelUpdateRequest,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
http,
|
||||
@@ -170,15 +172,21 @@ pub(crate) async fn maybe_build_local_admin_global_models_response(
|
||||
.ok()
|
||||
.map(|duration| duration.as_secs())
|
||||
.unwrap_or(0);
|
||||
(
|
||||
http::StatusCode::CREATED,
|
||||
Json(build_admin_global_model_response(
|
||||
&created,
|
||||
&provider_models,
|
||||
now_unix_secs,
|
||||
)),
|
||||
attach_admin_audit_response(
|
||||
(
|
||||
http::StatusCode::CREATED,
|
||||
Json(build_admin_global_model_response(
|
||||
&created,
|
||||
&provider_models,
|
||||
now_unix_secs,
|
||||
)),
|
||||
)
|
||||
.into_response(),
|
||||
"admin_global_model_created",
|
||||
"create_global_model",
|
||||
"global_model",
|
||||
&created.id,
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
None => (
|
||||
http::StatusCode::SERVICE_UNAVAILABLE,
|
||||
@@ -288,12 +296,18 @@ pub(crate) async fn maybe_build_local_admin_global_models_response(
|
||||
.ok()
|
||||
.map(|duration| duration.as_secs())
|
||||
.unwrap_or(0);
|
||||
Json(build_admin_global_model_response(
|
||||
&updated,
|
||||
&provider_models,
|
||||
now_unix_secs,
|
||||
))
|
||||
.into_response()
|
||||
attach_admin_audit_response(
|
||||
Json(build_admin_global_model_response(
|
||||
&updated,
|
||||
&provider_models,
|
||||
now_unix_secs,
|
||||
))
|
||||
.into_response(),
|
||||
"admin_global_model_updated",
|
||||
"update_global_model",
|
||||
"global_model",
|
||||
&updated.id,
|
||||
)
|
||||
}
|
||||
None => (
|
||||
http::StatusCode::NOT_FOUND,
|
||||
@@ -343,7 +357,13 @@ pub(crate) async fn maybe_build_local_admin_global_models_response(
|
||||
.into_response(),
|
||||
));
|
||||
}
|
||||
return Ok(Some(http::StatusCode::NO_CONTENT.into_response()));
|
||||
return Ok(Some(attach_admin_audit_response(
|
||||
http::StatusCode::NO_CONTENT.into_response(),
|
||||
"admin_global_model_deleted",
|
||||
"delete_global_model",
|
||||
"global_model",
|
||||
&existing.id,
|
||||
)));
|
||||
}
|
||||
|
||||
if decision.route_family.as_deref() == Some("global_models_manage")
|
||||
@@ -393,13 +413,17 @@ pub(crate) async fn maybe_build_local_admin_global_models_response(
|
||||
failed.push(json!({"id": existing.id, "error": "delete failed"}));
|
||||
}
|
||||
}
|
||||
return Ok(Some(
|
||||
return Ok(Some(attach_admin_audit_response(
|
||||
Json(json!({
|
||||
"success_count": success_count,
|
||||
"failed": failed,
|
||||
}))
|
||||
.into_response(),
|
||||
));
|
||||
"admin_global_models_batch_deleted",
|
||||
"batch_delete_global_models",
|
||||
"global_models_batch",
|
||||
"batch",
|
||||
)));
|
||||
}
|
||||
|
||||
if decision.route_family.as_deref() == Some("global_models_manage")
|
||||
@@ -458,7 +482,13 @@ pub(crate) async fn maybe_build_local_admin_global_models_response(
|
||||
));
|
||||
}
|
||||
};
|
||||
return Ok(Some(Json(payload).into_response()));
|
||||
return Ok(Some(attach_admin_audit_response(
|
||||
Json(payload).into_response(),
|
||||
"admin_global_model_assigned_to_providers",
|
||||
"assign_global_model_to_providers",
|
||||
"global_model",
|
||||
&global_model_id,
|
||||
)));
|
||||
}
|
||||
|
||||
if decision.route_family.as_deref() == Some("global_models_manage")
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
response::Response,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use super::ldap_shared::*;
|
||||
use crate::gateway::handlers::{
|
||||
use crate::handlers::{
|
||||
decrypt_catalog_secret_with_fallbacks, encrypt_catalog_secret_with_fallbacks,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayError};
|
||||
use crate::{AppState, GatewayError};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
||||
@@ -3,7 +3,9 @@ use super::ldap_builders::{
|
||||
AdminLdapConfigTestRequest, AdminLdapConfigUpdateRequest,
|
||||
};
|
||||
use super::ldap_shared::*;
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::admin::misc_helpers::attach_admin_audit_response;
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
http,
|
||||
@@ -29,12 +31,16 @@ pub(super) async fn maybe_build_local_admin_ldap_response(
|
||||
if request_context.request_method == http::Method::GET
|
||||
&& is_admin_ldap_config_root(&request_context.request_path) =>
|
||||
{
|
||||
return Ok(Some(
|
||||
return Ok(Some(attach_admin_audit_response(
|
||||
Json(build_admin_ldap_config_payload(
|
||||
state.get_ldap_module_config().await?.as_ref(),
|
||||
))
|
||||
.into_response(),
|
||||
));
|
||||
"admin_ldap_config_viewed",
|
||||
"view_ldap_config",
|
||||
"ldap_config",
|
||||
"ldap",
|
||||
)));
|
||||
}
|
||||
Some("set_config")
|
||||
if request_context.request_method == http::Method::PUT
|
||||
@@ -91,7 +97,13 @@ pub(super) async fn maybe_build_local_admin_ldap_response(
|
||||
"message": "缺少必要字段: server_url, bind_dn, base_dn, bind_password",
|
||||
}),
|
||||
};
|
||||
return Ok(Some(Json(response).into_response()));
|
||||
return Ok(Some(attach_admin_audit_response(
|
||||
Json(response).into_response(),
|
||||
"admin_ldap_connection_tested",
|
||||
"test_ldap_connection",
|
||||
"ldap_config",
|
||||
"ldap",
|
||||
)));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::gateway::handlers::{json_string_list, unix_secs_to_rfc3339};
|
||||
use crate::gateway::GatewayPublicRequestContext;
|
||||
use crate::audit::attach_admin_audit_event;
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::{json_string_list, unix_secs_to_rfc3339};
|
||||
use aether_data::repository::provider_catalog::StoredProviderCatalogKey;
|
||||
use axum::{
|
||||
body::Body,
|
||||
@@ -42,6 +43,17 @@ pub(crate) fn build_admin_proxy_auth_required_response(
|
||||
.into_response()
|
||||
}
|
||||
|
||||
pub(crate) fn attach_admin_audit_response(
|
||||
mut response: Response<Body>,
|
||||
event_name: &'static str,
|
||||
action: &'static str,
|
||||
target_type: &'static str,
|
||||
target_id: &str,
|
||||
) -> Response<Body> {
|
||||
attach_admin_audit_event(&mut response, event_name, action, target_type, target_id);
|
||||
response
|
||||
}
|
||||
|
||||
pub(crate) fn provider_catalog_key_supports_format(
|
||||
key: &StoredProviderCatalogKey,
|
||||
api_format: &str,
|
||||
|
||||
@@ -10,11 +10,11 @@ mod billing;
|
||||
mod catalog_write_helpers;
|
||||
mod core;
|
||||
mod endpoints;
|
||||
mod endpoints_health_helpers;
|
||||
pub(crate) mod endpoints_health_helpers;
|
||||
mod gemini_files;
|
||||
mod global_models;
|
||||
mod ldap;
|
||||
mod misc_helpers;
|
||||
pub(crate) mod misc_helpers;
|
||||
mod models_helpers;
|
||||
mod monitoring;
|
||||
mod oauth_helpers;
|
||||
@@ -26,17 +26,17 @@ mod provider_oauth_dispatch;
|
||||
#[path = "provider_oauth/quota.rs"]
|
||||
mod provider_oauth_quota;
|
||||
#[path = "provider_oauth/refresh.rs"]
|
||||
mod provider_oauth_refresh;
|
||||
pub(crate) mod provider_oauth_refresh;
|
||||
#[path = "provider_oauth/state.rs"]
|
||||
mod provider_oauth_state;
|
||||
mod provider_ops;
|
||||
pub(crate) mod provider_ops;
|
||||
mod provider_query;
|
||||
mod provider_strategy;
|
||||
mod providers;
|
||||
mod providers_helpers;
|
||||
mod proxy_nodes;
|
||||
mod security;
|
||||
mod stats;
|
||||
pub(crate) mod stats;
|
||||
mod usage;
|
||||
mod users;
|
||||
mod video_tasks;
|
||||
@@ -53,21 +53,28 @@ pub(crate) use self::core::maybe_build_local_admin_core_response;
|
||||
use self::core::*;
|
||||
pub(crate) use self::endpoints::maybe_build_local_admin_endpoints_response;
|
||||
use self::endpoints::*;
|
||||
pub(crate) use self::endpoints_health_helpers::build_admin_endpoint_health_status_payload;
|
||||
use self::endpoints_health_helpers::*;
|
||||
use self::endpoints_health_helpers::{
|
||||
build_admin_create_provider_endpoint_record, build_admin_endpoint_health_status_payload,
|
||||
build_admin_endpoint_payload, build_admin_health_summary_payload,
|
||||
build_admin_key_health_payload, build_admin_key_rpm_payload,
|
||||
build_admin_provider_endpoints_payload, build_admin_update_provider_endpoint_record,
|
||||
recover_admin_key_health, recover_all_admin_key_health,
|
||||
};
|
||||
pub(crate) use self::gemini_files::maybe_build_local_admin_gemini_files_response;
|
||||
use self::gemini_files::*;
|
||||
pub(crate) use self::global_models::maybe_build_local_admin_global_models_response;
|
||||
use self::global_models::*;
|
||||
pub(crate) use self::ldap::maybe_build_local_admin_ldap_response;
|
||||
use self::ldap::*;
|
||||
use self::misc_helpers::*;
|
||||
pub(crate) use self::misc_helpers::{
|
||||
build_admin_proxy_auth_required_response, build_unhandled_admin_proxy_response,
|
||||
provider_catalog_key_supports_format,
|
||||
use self::misc_helpers::{
|
||||
admin_default_body_rules_api_format, admin_endpoint_id, admin_health_key_id,
|
||||
admin_provider_id_for_endpoints, admin_recover_key_id, admin_rpm_key_id,
|
||||
attach_admin_audit_response, build_admin_provider_endpoint_response,
|
||||
endpoint_key_counts_by_format, endpoint_timestamp_or_now, json_truthy,
|
||||
key_api_formats_without_entry,
|
||||
};
|
||||
use self::models_helpers::*;
|
||||
pub(crate) use self::monitoring::maybe_build_local_admin_monitoring_root_response as maybe_build_local_admin_monitoring_response;
|
||||
pub(crate) use self::monitoring::maybe_build_local_admin_monitoring_response;
|
||||
use self::oauth_helpers::*;
|
||||
pub(crate) use self::payments::maybe_build_local_admin_payments_response;
|
||||
use self::payments::*;
|
||||
@@ -77,10 +84,13 @@ pub(crate) use self::provider_models::maybe_build_local_admin_provider_models_re
|
||||
use self::provider_models::*;
|
||||
pub(crate) use self::provider_oauth_dispatch::maybe_build_local_admin_provider_oauth_response;
|
||||
use self::provider_oauth_dispatch::*;
|
||||
use self::provider_oauth_quota::*;
|
||||
pub(crate) use self::provider_oauth_refresh::build_internal_control_error_response;
|
||||
use self::provider_oauth_refresh::*;
|
||||
use self::provider_oauth_state::*;
|
||||
use self::provider_oauth_quota::{
|
||||
normalize_string_id_list, refresh_antigravity_provider_quota_locally,
|
||||
refresh_codex_provider_quota_locally, refresh_kiro_provider_quota_locally,
|
||||
};
|
||||
use self::provider_oauth_refresh::{
|
||||
build_internal_control_error_response, normalize_provider_oauth_refresh_error_message,
|
||||
};
|
||||
pub(crate) use self::provider_ops::admin_provider_ops_local_action_response;
|
||||
pub(crate) use self::provider_ops::maybe_build_local_admin_provider_ops_response;
|
||||
pub(crate) use self::provider_query::maybe_build_local_admin_provider_query_response;
|
||||
@@ -94,11 +104,10 @@ pub(crate) use self::proxy_nodes::maybe_build_local_admin_proxy_nodes_response;
|
||||
use self::proxy_nodes::*;
|
||||
pub(crate) use self::security::maybe_build_local_admin_security_response;
|
||||
use self::security::*;
|
||||
use self::stats::*;
|
||||
pub(crate) use self::stats::{
|
||||
admin_stats_bad_request_response, list_usage_for_optional_range,
|
||||
maybe_build_local_admin_stats_response, parse_bounded_u32, round_to, AdminStatsTimeRange,
|
||||
AdminStatsUsageFilter,
|
||||
pub(crate) use self::stats::maybe_build_local_admin_stats_response;
|
||||
use self::stats::{
|
||||
aggregate_usage_stats, list_usage_for_optional_range, parse_bounded_u32, round_to,
|
||||
AdminStatsTimeRange, AdminStatsUsageFilter,
|
||||
};
|
||||
pub(crate) use self::usage::maybe_build_local_admin_usage_response;
|
||||
use self::usage::*;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::gateway::handlers::{
|
||||
use crate::handlers::{
|
||||
ADMIN_EXTERNAL_MODELS_CACHE_KEY, ADMIN_EXTERNAL_MODELS_CACHE_TTL_SECS,
|
||||
OFFICIAL_EXTERNAL_MODEL_PROVIDERS,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayError};
|
||||
use crate::{AppState, GatewayError};
|
||||
use serde_json::json;
|
||||
|
||||
fn mark_admin_external_models_official(mut payload: serde_json::Value) -> serde_json::Value {
|
||||
|
||||
@@ -3,11 +3,12 @@ use super::{
|
||||
admin_provider_model_effective_output_price, model_tiered_pricing_first_tier_value,
|
||||
timestamp_or_now,
|
||||
};
|
||||
use crate::gateway::handlers::json_string_list;
|
||||
use crate::gateway::AppState;
|
||||
use crate::handlers::json_string_list;
|
||||
use crate::AppState;
|
||||
use aether_data::repository::global_models::{
|
||||
AdminGlobalModelListQuery, StoredAdminGlobalModel, StoredAdminProviderModel,
|
||||
};
|
||||
use futures_util::stream::{self, StreamExt};
|
||||
use serde_json::json;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
@@ -79,6 +80,29 @@ fn build_admin_global_model_price_range(
|
||||
})
|
||||
}
|
||||
|
||||
async fn admin_global_model_provider_models_by_global_model_id(
|
||||
state: &AppState,
|
||||
global_model_ids: &[String],
|
||||
) -> BTreeMap<String, Vec<StoredAdminProviderModel>> {
|
||||
let state = state.clone();
|
||||
stream::iter(global_model_ids.iter().cloned().map(|global_model_id| {
|
||||
let state = state.clone();
|
||||
async move {
|
||||
let provider_models = state
|
||||
.list_admin_provider_models_by_global_model_id(&global_model_id)
|
||||
.await
|
||||
.ok()
|
||||
.unwrap_or_default();
|
||||
(global_model_id, provider_models)
|
||||
}
|
||||
}))
|
||||
.buffer_unordered(32)
|
||||
.collect::<Vec<_>>()
|
||||
.await
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn build_admin_global_model_response(
|
||||
global_model: &StoredAdminGlobalModel,
|
||||
provider_models: &[StoredAdminProviderModel],
|
||||
@@ -132,12 +156,16 @@ pub(crate) async fn build_admin_global_models_payload(
|
||||
.cmp(&right.name)
|
||||
.then_with(|| left.id.cmp(&right.id))
|
||||
});
|
||||
let global_model_ids = models
|
||||
.iter()
|
||||
.map(|model| model.id.clone())
|
||||
.collect::<Vec<_>>();
|
||||
let mut provider_models_by_global_model =
|
||||
admin_global_model_provider_models_by_global_model_id(state, &global_model_ids).await;
|
||||
let mut payload_models = Vec::with_capacity(models.len());
|
||||
for model in models {
|
||||
let provider_models = state
|
||||
.list_admin_provider_models_by_global_model_id(&model.id)
|
||||
.await
|
||||
.ok()
|
||||
let provider_models = provider_models_by_global_model
|
||||
.remove(&model.id)
|
||||
.unwrap_or_default();
|
||||
payload_models.push(build_admin_global_model_response(
|
||||
&model,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use super::resolve_admin_global_model_by_id_or_err;
|
||||
use crate::gateway::handlers::admin::provider_catalog_key_supports_format;
|
||||
use crate::gateway::handlers::{json_string_list, masked_catalog_api_key};
|
||||
use crate::gateway::scheduler::{is_provider_key_circuit_open, provider_key_health_score};
|
||||
use crate::gateway::AppState;
|
||||
use crate::handlers::admin::misc_helpers::provider_catalog_key_supports_format;
|
||||
use crate::handlers::{json_string_list, masked_catalog_api_key};
|
||||
use crate::scheduler::{is_provider_key_circuit_open, provider_key_health_score};
|
||||
use crate::AppState;
|
||||
use aether_data::repository::global_models::{
|
||||
AdminProviderModelListQuery, UpsertAdminProviderModelRecord,
|
||||
};
|
||||
|
||||
@@ -5,11 +5,11 @@ use super::{
|
||||
normalize_optional_price, normalize_required_trimmed_string,
|
||||
resolve_admin_global_model_by_id_or_err,
|
||||
};
|
||||
use crate::gateway::handlers::{
|
||||
use crate::handlers::{
|
||||
AdminGlobalModelCreateRequest, AdminGlobalModelUpdateRequest, AdminImportProviderModelsRequest,
|
||||
AdminProviderModelCreateRequest, AdminProviderModelUpdateRequest,
|
||||
};
|
||||
use crate::gateway::AppState;
|
||||
use crate::AppState;
|
||||
use aether_data::repository::global_models::{
|
||||
AdminProviderModelListQuery, CreateAdminGlobalModelRecord, StoredAdminGlobalModel,
|
||||
StoredAdminProviderModel, UpdateAdminGlobalModelRecord, UpsertAdminProviderModelRecord,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::gateway::handlers::unix_secs_to_rfc3339;
|
||||
use crate::gateway::{AppState, GatewayError};
|
||||
use crate::handlers::unix_secs_to_rfc3339;
|
||||
use crate::{AppState, GatewayError};
|
||||
use aether_data::repository::global_models::{
|
||||
AdminProviderModelListQuery, StoredAdminProviderModel,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::INTERNAL_GATEWAY_PATH_PREFIXES;
|
||||
use crate::gateway::handlers::{query_param_value, unix_secs_to_rfc3339};
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::{query_param_value, unix_secs_to_rfc3339};
|
||||
use crate::{AppState, GatewayError};
|
||||
use aether_crypto::decrypt_python_fernet_ciphertext;
|
||||
#[cfg(test)]
|
||||
use aether_crypto::DEVELOPMENT_ENCRYPTION_KEY;
|
||||
@@ -91,7 +92,7 @@ use self::common::{
|
||||
admin_monitoring_bad_request_response, admin_monitoring_data_unavailable_response,
|
||||
admin_monitoring_not_found_response, admin_monitoring_usage_is_error,
|
||||
admin_monitoring_user_behavior_user_id_from_path, AdminMonitoringCacheAffinityRecord,
|
||||
AdminMonitoringCacheSnapshot,
|
||||
AdminMonitoringCacheSnapshot, AdminMonitoringResilienceSnapshot,
|
||||
};
|
||||
use self::resilience::{
|
||||
build_admin_monitoring_reset_error_stats_response,
|
||||
@@ -105,7 +106,7 @@ use self::route_filters::{
|
||||
parse_admin_monitoring_username_filter,
|
||||
};
|
||||
use self::routes::{
|
||||
match_admin_monitoring_route, maybe_build_local_admin_monitoring_response, AdminMonitoringRoute,
|
||||
match_admin_monitoring_route, AdminMonitoringRoute,
|
||||
};
|
||||
use self::trace::{
|
||||
build_admin_monitoring_trace_provider_stats_response,
|
||||
@@ -120,7 +121,7 @@ const ADMIN_MONITORING_CACHE_AFFINITY_DEFAULT_TTL_SECS: u64 = 300;
|
||||
const ADMIN_MONITORING_CACHE_RESERVATION_RATIO: f64 = 0.1;
|
||||
const ADMIN_MONITORING_DYNAMIC_RESERVATION_PROBE_PHASE_REQUESTS: u64 = 100;
|
||||
|
||||
pub(crate) async fn maybe_build_local_admin_monitoring_root_response(
|
||||
pub(crate) async fn maybe_build_local_admin_monitoring_response(
|
||||
state: &AppState,
|
||||
request_context: &GatewayPublicRequestContext,
|
||||
) -> Result<Option<Response<Body>>, GatewayError> {
|
||||
|
||||
@@ -6,14 +6,15 @@ use super::{
|
||||
parse_admin_monitoring_hours, parse_admin_monitoring_limit, parse_admin_monitoring_offset,
|
||||
parse_admin_monitoring_username_filter,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::query::monitoring as monitoring_query;
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::Body,
|
||||
response::{IntoResponse, Response},
|
||||
Json,
|
||||
};
|
||||
use serde_json::json;
|
||||
use sqlx::Row;
|
||||
|
||||
fn build_admin_monitoring_audit_logs_payload(
|
||||
items: Vec<serde_json::Value>,
|
||||
@@ -121,86 +122,18 @@ pub(super) async fn build_admin_monitoring_audit_logs_response(
|
||||
.map(admin_monitoring_escape_like_pattern)
|
||||
.map(|value| format!("%{value}%"));
|
||||
|
||||
let total = sqlx::query_scalar::<_, i64>(
|
||||
r#"
|
||||
SELECT COUNT(*)
|
||||
FROM audit_logs AS a
|
||||
LEFT JOIN users AS u ON a.user_id = u.id
|
||||
WHERE a.created_at >= $1
|
||||
AND ($2::text IS NULL OR u.username ILIKE $2 ESCAPE '\')
|
||||
AND ($3::text IS NULL OR a.event_type = $3)
|
||||
"#,
|
||||
)
|
||||
.bind(cutoff_time)
|
||||
.bind(username_pattern.as_deref())
|
||||
.bind(event_type.as_deref())
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(format!("admin audit logs count failed: {err}")))?;
|
||||
|
||||
let rows = sqlx::query(
|
||||
r#"
|
||||
SELECT
|
||||
a.id,
|
||||
a.event_type,
|
||||
a.user_id,
|
||||
u.email AS user_email,
|
||||
u.username AS user_username,
|
||||
a.description,
|
||||
a.ip_address,
|
||||
a.status_code,
|
||||
a.error_message,
|
||||
a.event_metadata AS metadata,
|
||||
a.created_at
|
||||
FROM audit_logs AS a
|
||||
LEFT JOIN users AS u ON a.user_id = u.id
|
||||
WHERE a.created_at >= $1
|
||||
AND ($2::text IS NULL OR u.username ILIKE $2 ESCAPE '\')
|
||||
AND ($3::text IS NULL OR a.event_type = $3)
|
||||
ORDER BY a.created_at DESC
|
||||
LIMIT $4 OFFSET $5
|
||||
"#,
|
||||
)
|
||||
.bind(cutoff_time)
|
||||
.bind(username_pattern.as_deref())
|
||||
.bind(event_type.as_deref())
|
||||
.bind(i64::try_from(limit).unwrap_or(i64::MAX))
|
||||
.bind(i64::try_from(offset).unwrap_or(i64::MAX))
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(format!("admin audit logs read failed: {err}")))?;
|
||||
|
||||
let items = rows
|
||||
.into_iter()
|
||||
.map(|row| {
|
||||
let created_at = row
|
||||
.try_get::<chrono::DateTime<chrono::Utc>, _>("created_at")
|
||||
.ok()
|
||||
.map(|value| value.to_rfc3339());
|
||||
json!({
|
||||
"id": row.try_get::<String, _>("id").ok(),
|
||||
"event_type": row.try_get::<String, _>("event_type").ok(),
|
||||
"user_id": row.try_get::<Option<String>, _>("user_id").ok().flatten(),
|
||||
"user_email": row.try_get::<Option<String>, _>("user_email").ok().flatten(),
|
||||
"user_username": row.try_get::<Option<String>, _>("user_username").ok().flatten(),
|
||||
"description": row.try_get::<Option<String>, _>("description").ok().flatten(),
|
||||
"ip_address": row.try_get::<Option<String>, _>("ip_address").ok().flatten(),
|
||||
"status_code": row.try_get::<Option<i32>, _>("status_code").ok().flatten(),
|
||||
"error_message": row.try_get::<Option<String>, _>("error_message").ok().flatten(),
|
||||
"metadata": row.try_get::<Option<serde_json::Value>, _>("metadata").ok().flatten(),
|
||||
"created_at": created_at,
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(build_admin_monitoring_audit_logs_payload(
|
||||
items,
|
||||
usize::try_from(total.max(0)).unwrap_or(usize::MAX),
|
||||
let (items, total) = monitoring_query::list_admin_audit_logs(
|
||||
&pool,
|
||||
cutoff_time,
|
||||
username_pattern.as_deref(),
|
||||
event_type.as_deref(),
|
||||
limit,
|
||||
offset,
|
||||
username,
|
||||
event_type,
|
||||
days,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(build_admin_monitoring_audit_logs_payload(
|
||||
items, total, limit, offset, username, event_type, days,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -222,54 +155,7 @@ pub(super) async fn build_admin_monitoring_suspicious_activities_response(
|
||||
};
|
||||
|
||||
let cutoff_time = chrono::Utc::now() - chrono::Duration::hours(hours);
|
||||
let rows = sqlx::query(
|
||||
r#"
|
||||
SELECT
|
||||
id,
|
||||
event_type,
|
||||
user_id,
|
||||
description,
|
||||
ip_address,
|
||||
event_metadata AS metadata,
|
||||
created_at
|
||||
FROM audit_logs
|
||||
WHERE created_at >= $1
|
||||
AND event_type = ANY($2)
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 100
|
||||
"#,
|
||||
)
|
||||
.bind(cutoff_time)
|
||||
.bind(vec![
|
||||
"suspicious_activity",
|
||||
"unauthorized_access",
|
||||
"login_failed",
|
||||
"request_rate_limited",
|
||||
])
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
GatewayError::Internal(format!("admin suspicious activities read failed: {err}"))
|
||||
})?;
|
||||
|
||||
let activities = rows
|
||||
.into_iter()
|
||||
.map(|row| {
|
||||
let created_at = row
|
||||
.try_get::<chrono::DateTime<chrono::Utc>, _>("created_at")
|
||||
.ok()
|
||||
.map(|value| value.to_rfc3339());
|
||||
json!({
|
||||
"id": row.try_get::<String, _>("id").ok(),
|
||||
"event_type": row.try_get::<String, _>("event_type").ok(),
|
||||
"user_id": row.try_get::<Option<String>, _>("user_id").ok().flatten(),
|
||||
"description": row.try_get::<Option<String>, _>("description").ok().flatten(),
|
||||
"ip_address": row.try_get::<Option<String>, _>("ip_address").ok().flatten(),
|
||||
"metadata": row.try_get::<Option<serde_json::Value>, _>("metadata").ok().flatten(),
|
||||
"created_at": created_at,
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let activities = monitoring_query::list_admin_suspicious_activities(&pool, cutoff_time).await?;
|
||||
|
||||
Ok(build_admin_monitoring_suspicious_activities_payload(
|
||||
activities, hours,
|
||||
@@ -303,33 +189,9 @@ pub(super) async fn build_admin_monitoring_user_behavior_response(
|
||||
|
||||
let cutoff_time = chrono::Utc::now() - chrono::Duration::days(days);
|
||||
|
||||
let event_rows = sqlx::query(
|
||||
r#"
|
||||
SELECT event_type, COUNT(*)::bigint AS count
|
||||
FROM audit_logs
|
||||
WHERE user_id = $1
|
||||
AND created_at >= $2
|
||||
GROUP BY event_type
|
||||
"#,
|
||||
)
|
||||
.bind(&user_id)
|
||||
.bind(cutoff_time)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(format!("admin user behavior read failed: {err}")))?;
|
||||
|
||||
let event_counts = event_rows
|
||||
.into_iter()
|
||||
.filter_map(|row| {
|
||||
let event_type = row.try_get::<String, _>("event_type").ok()?;
|
||||
let count = row
|
||||
.try_get::<i64, _>("count")
|
||||
.ok()
|
||||
.and_then(|value| u64::try_from(value.max(0)).ok())
|
||||
.unwrap_or(0);
|
||||
Some((event_type, count))
|
||||
})
|
||||
.collect::<std::collections::BTreeMap<_, _>>();
|
||||
let event_counts =
|
||||
monitoring_query::read_admin_user_behavior_event_counts(&pool, &user_id, cutoff_time)
|
||||
.await?;
|
||||
|
||||
let failed_requests = event_counts
|
||||
.get("request_failed")
|
||||
|
||||
@@ -29,7 +29,8 @@ use super::{
|
||||
ADMIN_MONITORING_DYNAMIC_RESERVATION_STABLE_MIN_RESERVATION,
|
||||
ADMIN_MONITORING_REDIS_CACHE_CATEGORIES,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::Body,
|
||||
http,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::AdminMonitoringCacheAffinityRecord;
|
||||
use crate::gateway::{AppState, GatewayError};
|
||||
use crate::{AppState, GatewayError};
|
||||
|
||||
fn parse_admin_monitoring_cache_affinity_key(raw_key: &str) -> Option<(String, String, String)> {
|
||||
let parts = raw_key.split(':').collect::<Vec<_>>();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::AdminMonitoringCacheAffinityRecord;
|
||||
use crate::gateway::{AppState, GatewayError};
|
||||
use crate::{AppState, GatewayError};
|
||||
|
||||
pub(super) async fn admin_monitoring_list_export_api_key_records_by_ids(
|
||||
state: &AppState,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::gateway::AppState;
|
||||
use crate::AppState;
|
||||
use aether_crypto::decrypt_python_fernet_ciphertext;
|
||||
#[cfg(test)]
|
||||
use aether_crypto::DEVELOPMENT_ENCRYPTION_KEY;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::{
|
||||
ADMIN_MONITORING_CACHE_AFFINITY_REDIS_REQUIRED_DETAIL, ADMIN_MONITORING_REDIS_REQUIRED_DETAIL,
|
||||
};
|
||||
use crate::gateway::handlers::query_param_value;
|
||||
use crate::handlers::query_param_value;
|
||||
use axum::{
|
||||
body::Body,
|
||||
http,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::super::round_to;
|
||||
use super::cache_affinity::admin_monitoring_cache_affinity_record;
|
||||
use super::{AdminMonitoringCacheAffinityRecord, AdminMonitoringCacheSnapshot};
|
||||
use crate::gateway::{AppState, GatewayError};
|
||||
use crate::handlers::round_to;
|
||||
use crate::{AppState, GatewayError};
|
||||
|
||||
async fn count_admin_monitoring_cache_affinity_entries(state: &AppState) -> usize {
|
||||
let Some(runner) = state.redis_kv_runner() else {
|
||||
|
||||
@@ -35,6 +35,16 @@ pub(super) struct AdminMonitoringCacheAffinityRecord {
|
||||
pub(super) request_count: u64,
|
||||
}
|
||||
|
||||
pub(super) struct AdminMonitoringResilienceSnapshot {
|
||||
pub(super) timestamp: chrono::DateTime<chrono::Utc>,
|
||||
pub(super) health_score: i64,
|
||||
pub(super) status: &'static str,
|
||||
pub(super) error_statistics: serde_json::Value,
|
||||
pub(super) recent_errors: Vec<serde_json::Value>,
|
||||
pub(super) recommendations: Vec<String>,
|
||||
pub(super) previous_stats: serde_json::Value,
|
||||
}
|
||||
|
||||
pub(super) fn admin_monitoring_data_unavailable_response() -> Response<Body> {
|
||||
(
|
||||
http::StatusCode::SERVICE_UNAVAILABLE,
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
use super::{admin_monitoring_bad_request_response, admin_monitoring_usage_is_error};
|
||||
use crate::gateway::handlers::{
|
||||
use super::{
|
||||
admin_monitoring_bad_request_response, admin_monitoring_usage_is_error,
|
||||
AdminMonitoringResilienceSnapshot,
|
||||
};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::{
|
||||
provider_key_health_summary, query_param_value, unix_secs_to_rfc3339,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::Body,
|
||||
response::{IntoResponse, Response},
|
||||
@@ -11,16 +15,6 @@ use axum::{
|
||||
use serde_json::json;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
struct AdminMonitoringResilienceSnapshot {
|
||||
timestamp: chrono::DateTime<chrono::Utc>,
|
||||
health_score: i64,
|
||||
status: &'static str,
|
||||
error_statistics: serde_json::Value,
|
||||
recent_errors: Vec<serde_json::Value>,
|
||||
recommendations: Vec<String>,
|
||||
previous_stats: serde_json::Value,
|
||||
}
|
||||
|
||||
fn build_admin_monitoring_resilience_recommendations(
|
||||
total_errors: usize,
|
||||
health_score: i64,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::gateway::handlers::query_param_value;
|
||||
use crate::handlers::query_param_value;
|
||||
|
||||
pub(super) fn admin_monitoring_escape_like_pattern(value: &str) -> String {
|
||||
value
|
||||
|
||||
@@ -20,7 +20,9 @@ use super::{
|
||||
build_admin_monitoring_trace_provider_stats_response,
|
||||
build_admin_monitoring_trace_request_response, build_admin_monitoring_user_behavior_response,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::admin::misc_helpers::attach_admin_audit_response;
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{body::Body, http, response::Response};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
@@ -63,9 +65,13 @@ pub(super) async fn maybe_build_local_admin_monitoring_response(
|
||||
};
|
||||
|
||||
match route {
|
||||
AdminMonitoringRoute::AuditLogs => Ok(Some(
|
||||
AdminMonitoringRoute::AuditLogs => Ok(Some(attach_admin_audit_response(
|
||||
build_admin_monitoring_audit_logs_response(state, request_context).await?,
|
||||
)),
|
||||
"admin_monitoring_audit_logs_viewed",
|
||||
"view_admin_audit_logs",
|
||||
"audit_log",
|
||||
&admin_monitoring_audit_target_id(request_context),
|
||||
))),
|
||||
AdminMonitoringRoute::ResilienceStatus => Ok(Some(
|
||||
build_admin_monitoring_resilience_status_response(state).await?,
|
||||
)),
|
||||
@@ -79,12 +85,20 @@ pub(super) async fn maybe_build_local_admin_monitoring_response(
|
||||
AdminMonitoringRoute::CacheStats => Ok(Some(
|
||||
build_admin_monitoring_cache_stats_response(state).await?,
|
||||
)),
|
||||
AdminMonitoringRoute::CacheAffinities => Ok(Some(
|
||||
AdminMonitoringRoute::CacheAffinities => Ok(Some(attach_admin_audit_response(
|
||||
build_admin_monitoring_cache_affinities_response(state, request_context).await?,
|
||||
)),
|
||||
AdminMonitoringRoute::CacheAffinity => Ok(Some(
|
||||
"admin_monitoring_cache_affinities_viewed",
|
||||
"view_cache_affinities",
|
||||
"cache_affinity",
|
||||
&admin_monitoring_audit_target_id(request_context),
|
||||
))),
|
||||
AdminMonitoringRoute::CacheAffinity => Ok(Some(attach_admin_audit_response(
|
||||
build_admin_monitoring_cache_affinity_response(state, request_context).await?,
|
||||
)),
|
||||
"admin_monitoring_cache_affinity_viewed",
|
||||
"view_cache_affinity",
|
||||
"cache_affinity",
|
||||
&admin_monitoring_audit_target_id(request_context),
|
||||
))),
|
||||
AdminMonitoringRoute::CacheUsersDelete => Ok(Some(
|
||||
build_admin_monitoring_cache_users_delete_response(state, request_context).await?,
|
||||
)),
|
||||
@@ -126,18 +140,43 @@ pub(super) async fn maybe_build_local_admin_monitoring_response(
|
||||
AdminMonitoringRoute::SystemStatus => Ok(Some(
|
||||
build_admin_monitoring_system_status_response(state).await?,
|
||||
)),
|
||||
AdminMonitoringRoute::SuspiciousActivities => Ok(Some(
|
||||
AdminMonitoringRoute::SuspiciousActivities => Ok(Some(attach_admin_audit_response(
|
||||
build_admin_monitoring_suspicious_activities_response(state, request_context).await?,
|
||||
)),
|
||||
AdminMonitoringRoute::UserBehavior => Ok(Some(
|
||||
"admin_monitoring_suspicious_activities_viewed",
|
||||
"view_suspicious_activities",
|
||||
"suspicious_activity",
|
||||
&admin_monitoring_audit_target_id(request_context),
|
||||
))),
|
||||
AdminMonitoringRoute::UserBehavior => Ok(Some(attach_admin_audit_response(
|
||||
build_admin_monitoring_user_behavior_response(state, request_context).await?,
|
||||
)),
|
||||
AdminMonitoringRoute::TraceRequest => Ok(Some(
|
||||
"admin_monitoring_user_behavior_viewed",
|
||||
"view_user_behavior",
|
||||
"user",
|
||||
&admin_monitoring_audit_target_id(request_context),
|
||||
))),
|
||||
AdminMonitoringRoute::TraceRequest => Ok(Some(attach_admin_audit_response(
|
||||
build_admin_monitoring_trace_request_response(state, request_context).await?,
|
||||
)),
|
||||
AdminMonitoringRoute::TraceProviderStats => Ok(Some(
|
||||
"admin_monitoring_request_trace_viewed",
|
||||
"view_request_trace",
|
||||
"request_trace",
|
||||
&admin_monitoring_audit_target_id(request_context),
|
||||
))),
|
||||
AdminMonitoringRoute::TraceProviderStats => Ok(Some(attach_admin_audit_response(
|
||||
build_admin_monitoring_trace_provider_stats_response(state, request_context).await?,
|
||||
)),
|
||||
"admin_monitoring_provider_trace_stats_viewed",
|
||||
"view_provider_trace_stats",
|
||||
"provider",
|
||||
&admin_monitoring_audit_target_id(request_context),
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn admin_monitoring_audit_target_id(request_context: &GatewayPublicRequestContext) -> String {
|
||||
match request_context.request_query_string.as_deref() {
|
||||
Some(query) if !query.trim().is_empty() => {
|
||||
format!("{}?{query}", request_context.request_path)
|
||||
}
|
||||
_ => request_context.request_path.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::gateway::GatewayPublicRequestContext;
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use aether_crypto::{encrypt_python_fernet_plaintext, DEVELOPMENT_ENCRYPTION_KEY};
|
||||
use axum::http::{self, Uri};
|
||||
use serde_json::json;
|
||||
|
||||
@@ -58,7 +58,7 @@ async fn admin_monitoring_cache_affinity_returns_not_found_without_runtime_or_te
|
||||
let state = AppState::new()
|
||||
.expect("state should build")
|
||||
.with_data_state_for_tests(
|
||||
crate::gateway::gateway_data::GatewayDataState::with_user_reader_for_tests(
|
||||
crate::data::GatewayDataState::with_user_reader_for_tests(
|
||||
user_repository,
|
||||
)
|
||||
.with_auth_api_key_reader(auth_repository),
|
||||
@@ -106,7 +106,7 @@ async fn admin_monitoring_cache_affinities_and_affinity_return_local_payload_fro
|
||||
let state = AppState::new()
|
||||
.expect("state should build")
|
||||
.with_data_state_for_tests(
|
||||
crate::gateway::gateway_data::GatewayDataState::with_provider_catalog_reader_for_tests(
|
||||
crate::data::GatewayDataState::with_provider_catalog_reader_for_tests(
|
||||
provider_catalog,
|
||||
)
|
||||
.with_user_reader(user_repository)
|
||||
@@ -212,7 +212,7 @@ async fn admin_monitoring_cache_users_delete_returns_local_payload_from_test_sto
|
||||
let state = AppState::new()
|
||||
.expect("state should build")
|
||||
.with_data_state_for_tests(
|
||||
crate::gateway::gateway_data::GatewayDataState::with_user_reader_for_tests(
|
||||
crate::data::GatewayDataState::with_user_reader_for_tests(
|
||||
user_repository,
|
||||
)
|
||||
.with_auth_api_key_reader(auth_repository),
|
||||
@@ -600,7 +600,7 @@ async fn admin_monitoring_cache_affinity_delete_returns_local_payload_from_test_
|
||||
let state = AppState::new()
|
||||
.expect("state should build")
|
||||
.with_data_state_for_tests(
|
||||
crate::gateway::gateway_data::GatewayDataState::with_user_reader_for_tests(
|
||||
crate::data::GatewayDataState::with_user_reader_for_tests(
|
||||
user_repository,
|
||||
)
|
||||
.with_auth_api_key_reader(auth_repository),
|
||||
@@ -720,7 +720,7 @@ async fn admin_monitoring_cache_metrics_returns_local_payload() {
|
||||
let state = AppState::new()
|
||||
.expect("state should build")
|
||||
.with_data_state_for_tests(
|
||||
crate::gateway::gateway_data::GatewayDataState::with_usage_reader_for_tests(
|
||||
crate::data::GatewayDataState::with_usage_reader_for_tests(
|
||||
usage_repository,
|
||||
)
|
||||
.with_system_config_values_for_tests([
|
||||
@@ -852,7 +852,7 @@ async fn admin_monitoring_reset_error_stats_returns_local_payload_and_clears_fut
|
||||
let state = AppState::new()
|
||||
.expect("state should build")
|
||||
.with_data_state_for_tests(
|
||||
crate::gateway::gateway_data::GatewayDataState::with_provider_catalog_and_usage_reader_for_tests(
|
||||
crate::data::GatewayDataState::with_provider_catalog_and_usage_reader_for_tests(
|
||||
provider_catalog,
|
||||
usage_repository,
|
||||
),
|
||||
@@ -969,7 +969,7 @@ async fn admin_monitoring_circuit_history_returns_local_payload() {
|
||||
let state = AppState::new()
|
||||
.expect("state should build")
|
||||
.with_data_state_for_tests(
|
||||
crate::gateway::gateway_data::GatewayDataState::with_provider_catalog_reader_for_tests(
|
||||
crate::data::GatewayDataState::with_provider_catalog_reader_for_tests(
|
||||
provider_catalog,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@ use super::super::{
|
||||
match_admin_monitoring_route, maybe_build_local_admin_monitoring_response,
|
||||
AdminMonitoringRoute, ADMIN_MONITORING_REDIS_REQUIRED_DETAIL,
|
||||
};
|
||||
use crate::gateway::AppState;
|
||||
use crate::AppState;
|
||||
use aether_data::repository::provider_catalog::InMemoryProviderCatalogReadRepository;
|
||||
use aether_data::repository::usage::InMemoryUsageReadRepository;
|
||||
use axum::body::to_bytes;
|
||||
@@ -217,7 +217,7 @@ async fn admin_monitoring_resilience_status_returns_local_payload() {
|
||||
let state = AppState::new()
|
||||
.expect("state should build")
|
||||
.with_data_state_for_tests(
|
||||
crate::gateway::gateway_data::GatewayDataState::with_provider_catalog_and_usage_reader_for_tests(
|
||||
crate::data::GatewayDataState::with_provider_catalog_and_usage_reader_for_tests(
|
||||
provider_catalog,
|
||||
usage_repository,
|
||||
),
|
||||
@@ -288,7 +288,7 @@ async fn admin_monitoring_cache_stats_returns_local_payload() {
|
||||
let state = AppState::new()
|
||||
.expect("state should build")
|
||||
.with_data_state_for_tests(
|
||||
crate::gateway::gateway_data::GatewayDataState::with_usage_reader_for_tests(
|
||||
crate::data::GatewayDataState::with_usage_reader_for_tests(
|
||||
usage_repository,
|
||||
)
|
||||
.with_system_config_values_for_tests([
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::super::maybe_build_local_admin_monitoring_response;
|
||||
use super::super::test_support::{
|
||||
request_context, sample_candidate, sample_endpoint, sample_key, sample_provider,
|
||||
};
|
||||
use crate::gateway::AppState;
|
||||
use crate::AppState;
|
||||
use axum::body::to_bytes;
|
||||
use serde_json::json;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -2,8 +2,9 @@ use super::{
|
||||
admin_monitoring_bad_request_response, admin_monitoring_not_found_response,
|
||||
parse_admin_monitoring_limit,
|
||||
};
|
||||
use crate::gateway::handlers::{query_param_value, unix_secs_to_rfc3339};
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::{query_param_value, unix_secs_to_rfc3339};
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::Body,
|
||||
response::{IntoResponse, Response},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::gateway::handlers::{
|
||||
use crate::handlers::{
|
||||
encrypt_catalog_secret_with_fallbacks, AdminOAuthProviderUpsertRequest,
|
||||
};
|
||||
use crate::gateway::AppState;
|
||||
use crate::AppState;
|
||||
use aether_data::repository::oauth_providers::{
|
||||
EncryptedSecretUpdate, UpsertOAuthProviderConfigRecord,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{body::Body, response::Response};
|
||||
|
||||
#[path = "payment/postgres.rs"]
|
||||
|
||||
@@ -3,15 +3,15 @@ use super::{
|
||||
build_admin_payments_bad_request_response, parse_admin_payments_limit,
|
||||
parse_admin_payments_offset,
|
||||
};
|
||||
use crate::gateway::handlers::query_param_value;
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::query_param_value;
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::Body,
|
||||
response::{IntoResponse, Response},
|
||||
Json,
|
||||
};
|
||||
use serde_json::json;
|
||||
use sqlx::Row;
|
||||
|
||||
pub(super) async fn maybe_build_local_admin_payment_callbacks_response(
|
||||
state: &AppState,
|
||||
@@ -41,85 +41,16 @@ async fn build_admin_payment_callbacks_response(
|
||||
};
|
||||
let payment_method = query_param_value(query, "payment_method");
|
||||
|
||||
if let Some((items, total)) = state
|
||||
let (items, total) = state
|
||||
.list_admin_payment_callbacks(payment_method.as_deref(), limit, offset)
|
||||
.await?
|
||||
{
|
||||
return Ok(Json(json!({
|
||||
"items": items
|
||||
.iter()
|
||||
.map(build_admin_payment_callback_payload_from_record)
|
||||
.collect::<Vec<_>>(),
|
||||
"total": total,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
}))
|
||||
.into_response());
|
||||
}
|
||||
|
||||
let mut total = 0_u64;
|
||||
let mut items = Vec::new();
|
||||
if let Some(pool) = state.postgres_pool() {
|
||||
let count_row = sqlx::query(
|
||||
r#"
|
||||
SELECT COUNT(*) AS total
|
||||
FROM payment_callbacks
|
||||
WHERE ($1::TEXT IS NULL OR payment_method = $1)
|
||||
"#,
|
||||
)
|
||||
.bind(payment_method.as_deref())
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))?;
|
||||
total = count_row
|
||||
.try_get::<i64, _>("total")
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))?
|
||||
.max(0) as u64;
|
||||
let rows = sqlx::query(
|
||||
r#"
|
||||
SELECT
|
||||
id,
|
||||
payment_order_id,
|
||||
payment_method,
|
||||
callback_key,
|
||||
order_no,
|
||||
gateway_order_id,
|
||||
payload_hash,
|
||||
signature_valid,
|
||||
status,
|
||||
payload,
|
||||
error_message,
|
||||
CAST(EXTRACT(EPOCH FROM created_at) AS BIGINT) AS created_at_unix_secs,
|
||||
CAST(EXTRACT(EPOCH FROM processed_at) AS BIGINT) AS processed_at_unix_secs
|
||||
FROM payment_callbacks
|
||||
WHERE ($1::TEXT IS NULL OR payment_method = $1)
|
||||
ORDER BY created_at DESC
|
||||
OFFSET $2
|
||||
LIMIT $3
|
||||
"#,
|
||||
)
|
||||
.bind(payment_method.as_deref())
|
||||
.bind(i64::try_from(offset).map_err(|err| GatewayError::Internal(err.to_string()))?)
|
||||
.bind(i64::try_from(limit).map_err(|err| GatewayError::Internal(err.to_string()))?)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))?;
|
||||
items = rows
|
||||
.iter()
|
||||
.map(build_admin_payment_callback_payload)
|
||||
.collect::<Result<Vec<_>, GatewayError>>()?;
|
||||
} else {
|
||||
return Ok(Json(json!({
|
||||
"items": [],
|
||||
"total": 0,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
}))
|
||||
.into_response());
|
||||
}
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(Json(json!({
|
||||
"items": items,
|
||||
"items": items
|
||||
.iter()
|
||||
.map(build_admin_payment_callback_payload_from_record)
|
||||
.collect::<Vec<_>>(),
|
||||
"total": total,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
|
||||
@@ -7,8 +7,10 @@ use super::{
|
||||
normalize_admin_payment_positive_number, parse_admin_payments_limit,
|
||||
parse_admin_payments_offset, AdminPaymentOrderCreditRequest,
|
||||
};
|
||||
use crate::gateway::handlers::query_param_value;
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::admin::misc_helpers::attach_admin_audit_response;
|
||||
use crate::handlers::query_param_value;
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::Body,
|
||||
response::{IntoResponse, Response},
|
||||
@@ -90,17 +92,17 @@ async fn build_admin_payment_get_order_response(
|
||||
return Ok(build_admin_payment_order_not_found_response());
|
||||
};
|
||||
match state.read_admin_payment_order(&order_id).await? {
|
||||
crate::gateway::AdminWalletMutationOutcome::Applied(order) => Ok(Json(json!({
|
||||
crate::AdminWalletMutationOutcome::Applied(order) => Ok(Json(json!({
|
||||
"order": build_admin_payment_order_payload(&order),
|
||||
}))
|
||||
.into_response()),
|
||||
crate::gateway::AdminWalletMutationOutcome::NotFound => {
|
||||
crate::AdminWalletMutationOutcome::NotFound => {
|
||||
Ok(build_admin_payment_order_not_found_response())
|
||||
}
|
||||
crate::gateway::AdminWalletMutationOutcome::Invalid(detail) => {
|
||||
crate::AdminWalletMutationOutcome::Invalid(detail) => {
|
||||
Ok(build_admin_payments_bad_request_response(detail))
|
||||
}
|
||||
crate::gateway::AdminWalletMutationOutcome::Unavailable => {
|
||||
crate::AdminWalletMutationOutcome::Unavailable => {
|
||||
Ok(build_admin_payments_backend_unavailable_response(
|
||||
"Payment order read backend unavailable",
|
||||
))
|
||||
@@ -118,18 +120,26 @@ async fn build_admin_payment_expire_order_response(
|
||||
return Ok(build_admin_payment_order_not_found_response());
|
||||
};
|
||||
match state.admin_expire_payment_order(&order_id).await? {
|
||||
crate::gateway::AdminWalletMutationOutcome::Applied((order, expired)) => Ok(Json(json!({
|
||||
"order": build_admin_payment_order_payload(&order),
|
||||
"expired": expired,
|
||||
}))
|
||||
.into_response()),
|
||||
crate::gateway::AdminWalletMutationOutcome::NotFound => {
|
||||
crate::AdminWalletMutationOutcome::Applied((order, expired)) => {
|
||||
Ok(attach_admin_audit_response(
|
||||
Json(json!({
|
||||
"order": build_admin_payment_order_payload(&order),
|
||||
"expired": expired,
|
||||
}))
|
||||
.into_response(),
|
||||
"admin_payment_order_expired",
|
||||
"expire_payment_order",
|
||||
"payment_order",
|
||||
&order_id,
|
||||
))
|
||||
}
|
||||
crate::AdminWalletMutationOutcome::NotFound => {
|
||||
Ok(build_admin_payment_order_not_found_response())
|
||||
}
|
||||
crate::gateway::AdminWalletMutationOutcome::Invalid(detail) => {
|
||||
crate::AdminWalletMutationOutcome::Invalid(detail) => {
|
||||
Ok(build_admin_payments_bad_request_response(detail))
|
||||
}
|
||||
crate::gateway::AdminWalletMutationOutcome::Unavailable => {
|
||||
crate::AdminWalletMutationOutcome::Unavailable => {
|
||||
Ok(build_admin_payments_backend_unavailable_response(
|
||||
"Payment order write backend unavailable",
|
||||
))
|
||||
@@ -204,18 +214,26 @@ async fn build_admin_payment_credit_order_response(
|
||||
)
|
||||
.await?
|
||||
{
|
||||
crate::gateway::AdminWalletMutationOutcome::Applied((order, credited)) => Ok(Json(json!({
|
||||
"order": build_admin_payment_order_payload(&order),
|
||||
"credited": credited,
|
||||
}))
|
||||
.into_response()),
|
||||
crate::gateway::AdminWalletMutationOutcome::NotFound => {
|
||||
crate::AdminWalletMutationOutcome::Applied((order, credited)) => {
|
||||
Ok(attach_admin_audit_response(
|
||||
Json(json!({
|
||||
"order": build_admin_payment_order_payload(&order),
|
||||
"credited": credited,
|
||||
}))
|
||||
.into_response(),
|
||||
"admin_payment_order_credited",
|
||||
"credit_payment_order",
|
||||
"payment_order",
|
||||
&order_id,
|
||||
))
|
||||
}
|
||||
crate::AdminWalletMutationOutcome::NotFound => {
|
||||
Ok(build_admin_payment_order_not_found_response())
|
||||
}
|
||||
crate::gateway::AdminWalletMutationOutcome::Invalid(detail) => {
|
||||
crate::AdminWalletMutationOutcome::Invalid(detail) => {
|
||||
Ok(build_admin_payments_bad_request_response(detail))
|
||||
}
|
||||
crate::gateway::AdminWalletMutationOutcome::Unavailable => {
|
||||
crate::AdminWalletMutationOutcome::Unavailable => {
|
||||
Ok(build_admin_payments_backend_unavailable_response(
|
||||
"Payment order write backend unavailable",
|
||||
))
|
||||
@@ -233,17 +251,25 @@ async fn build_admin_payment_fail_order_response(
|
||||
return Ok(build_admin_payment_order_not_found_response());
|
||||
};
|
||||
match state.admin_fail_payment_order(&order_id).await? {
|
||||
crate::gateway::AdminWalletMutationOutcome::Applied(order) => Ok(Json(json!({
|
||||
"order": build_admin_payment_order_payload(&order),
|
||||
}))
|
||||
.into_response()),
|
||||
crate::gateway::AdminWalletMutationOutcome::NotFound => {
|
||||
crate::AdminWalletMutationOutcome::Applied(order) => {
|
||||
Ok(attach_admin_audit_response(
|
||||
Json(json!({
|
||||
"order": build_admin_payment_order_payload(&order),
|
||||
}))
|
||||
.into_response(),
|
||||
"admin_payment_order_failed",
|
||||
"fail_payment_order",
|
||||
"payment_order",
|
||||
&order_id,
|
||||
))
|
||||
}
|
||||
crate::AdminWalletMutationOutcome::NotFound => {
|
||||
Ok(build_admin_payment_order_not_found_response())
|
||||
}
|
||||
crate::gateway::AdminWalletMutationOutcome::Invalid(detail) => {
|
||||
crate::AdminWalletMutationOutcome::Invalid(detail) => {
|
||||
Ok(build_admin_payments_bad_request_response(detail))
|
||||
}
|
||||
crate::gateway::AdminWalletMutationOutcome::Unavailable => {
|
||||
crate::AdminWalletMutationOutcome::Unavailable => {
|
||||
Ok(build_admin_payments_backend_unavailable_response(
|
||||
"Payment order write backend unavailable",
|
||||
))
|
||||
|
||||
@@ -3,7 +3,8 @@ use super::{
|
||||
payments_callbacks::maybe_build_local_admin_payment_callbacks_response,
|
||||
payments_orders::maybe_build_local_admin_payment_orders_response,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{body::Body, http, response::Response};
|
||||
|
||||
pub(super) async fn maybe_build_local_admin_payments_response(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::gateway::handlers::{query_param_value, unix_secs_to_rfc3339};
|
||||
use crate::gateway::{GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::{query_param_value, unix_secs_to_rfc3339};
|
||||
use crate::GatewayError;
|
||||
use axum::{
|
||||
body::Body,
|
||||
http,
|
||||
@@ -196,7 +197,7 @@ pub(super) fn admin_payment_effective_status(
|
||||
}
|
||||
|
||||
pub(super) fn build_admin_payment_order_payload(
|
||||
record: &crate::gateway::AdminWalletPaymentOrderRecord,
|
||||
record: &crate::AdminWalletPaymentOrderRecord,
|
||||
) -> serde_json::Value {
|
||||
json!({
|
||||
"id": record.id,
|
||||
@@ -249,7 +250,7 @@ pub(super) fn build_admin_payment_callback_payload(
|
||||
}
|
||||
|
||||
pub(super) fn build_admin_payment_callback_payload_from_record(
|
||||
record: &crate::gateway::state::AdminPaymentCallbackRecord,
|
||||
record: &crate::state::AdminPaymentCallbackRecord,
|
||||
) -> serde_json::Value {
|
||||
json!({
|
||||
"id": record.id,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::gateway::handlers::query_param_value;
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::query_param_value;
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
http,
|
||||
|
||||
@@ -6,10 +6,10 @@ use super::{
|
||||
ADMIN_POOL_PROVIDER_CATALOG_WRITER_UNAVAILABLE_DETAIL,
|
||||
};
|
||||
use super::{pool_payloads, pool_selection};
|
||||
use crate::gateway::handlers::encrypt_catalog_secret_with_fallbacks;
|
||||
use crate::gateway::{
|
||||
AppState, GatewayError, GatewayPublicRequestContext, LocalProviderDeleteTaskState,
|
||||
};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::admin::misc_helpers::attach_admin_audit_response;
|
||||
use crate::handlers::encrypt_catalog_secret_with_fallbacks;
|
||||
use crate::{AppState, GatewayError, LocalProviderDeleteTaskState};
|
||||
use aether_data::repository::provider_catalog::{
|
||||
StoredProviderCatalogEndpoint, StoredProviderCatalogKey,
|
||||
};
|
||||
@@ -90,6 +90,31 @@ fn build_admin_pool_batch_delete_task_payload(
|
||||
})
|
||||
}
|
||||
|
||||
fn attach_admin_pool_batch_delete_task_terminal_audit(
|
||||
provider_id: &str,
|
||||
task_id: &str,
|
||||
task_status: &str,
|
||||
response: Response<Body>,
|
||||
) -> Response<Body> {
|
||||
match task_status {
|
||||
"completed" => attach_admin_audit_response(
|
||||
response,
|
||||
"admin_pool_batch_delete_task_completed_viewed",
|
||||
"view_pool_batch_delete_task_terminal_state",
|
||||
"provider_key_batch_delete_task",
|
||||
&format!("{provider_id}:{task_id}"),
|
||||
),
|
||||
"failed" => attach_admin_audit_response(
|
||||
response,
|
||||
"admin_pool_batch_delete_task_failed_viewed",
|
||||
"view_pool_batch_delete_task_terminal_state",
|
||||
"provider_key_batch_delete_task",
|
||||
&format!("{provider_id}:{task_id}"),
|
||||
),
|
||||
_ => response,
|
||||
}
|
||||
}
|
||||
|
||||
fn admin_pool_resolved_api_formats(
|
||||
endpoints: &[StoredProviderCatalogEndpoint],
|
||||
existing_keys: &[StoredProviderCatalogKey],
|
||||
@@ -547,7 +572,12 @@ async fn build_admin_pool_batch_delete_task_status_response(
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Json(build_admin_pool_batch_delete_task_payload(&task)).into_response())
|
||||
Ok(attach_admin_pool_batch_delete_task_terminal_audit(
|
||||
&provider_id,
|
||||
&task_id,
|
||||
task.status.as_str(),
|
||||
Json(build_admin_pool_batch_delete_task_payload(&task)).into_response(),
|
||||
))
|
||||
}
|
||||
|
||||
pub(super) async fn maybe_build_local_admin_pool_batch_response(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::gateway::handlers::{
|
||||
use crate::handlers::{
|
||||
unix_secs_to_rfc3339, AdminProviderPoolConfig, AdminProviderPoolRuntimeState,
|
||||
};
|
||||
use aether_data::repository::provider_catalog::StoredProviderCatalogKey;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::super::{
|
||||
admin_provider_pool_config, read_admin_provider_pool_cooldown_count,
|
||||
read_admin_provider_pool_cooldown_key_ids, read_admin_provider_pool_runtime_state,
|
||||
admin_provider_pool_config, read_admin_provider_pool_cooldown_key_ids,
|
||||
read_admin_provider_pool_runtime_state,
|
||||
};
|
||||
use super::{
|
||||
admin_pool_provider_id_from_path, build_admin_pool_error_response, parse_admin_pool_page,
|
||||
@@ -8,8 +8,9 @@ use super::{
|
||||
AdminPoolResolveSelectionRequest, ADMIN_POOL_PROVIDER_CATALOG_READER_UNAVAILABLE_DETAIL,
|
||||
};
|
||||
use super::{pool_payloads, pool_selection};
|
||||
use crate::gateway::handlers::AdminProviderPoolRuntimeState;
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::AdminProviderPoolRuntimeState;
|
||||
use crate::{AppState, GatewayError};
|
||||
use aether_data::repository::provider_catalog::ProviderCatalogKeyListQuery;
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
@@ -46,17 +47,22 @@ async fn build_admin_pool_overview_payload(
|
||||
.map(|item| (item.provider_id.clone(), item))
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
let redis_runner = state.redis_kv_runner();
|
||||
let cooldown_counts_by_provider = match redis_runner.as_ref() {
|
||||
Some(runner) if !provider_ids.is_empty() => {
|
||||
super::super::read_admin_provider_pool_cooldown_counts(runner, &provider_ids).await
|
||||
}
|
||||
_ => BTreeMap::new(),
|
||||
};
|
||||
|
||||
let mut items = Vec::with_capacity(pool_enabled_providers.len());
|
||||
for (provider, _pool_config) in pool_enabled_providers {
|
||||
let stats = key_stats_by_provider.get(&provider.id);
|
||||
let total_keys = stats.map(|item| item.total_keys as usize).unwrap_or(0);
|
||||
let active_keys = stats.map(|item| item.active_keys as usize).unwrap_or(0);
|
||||
let cooldown_count = if let Some(runner) = redis_runner.as_ref() {
|
||||
read_admin_provider_pool_cooldown_count(runner, &provider.id).await
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let cooldown_count = cooldown_counts_by_provider
|
||||
.get(&provider.id)
|
||||
.copied()
|
||||
.unwrap_or(0);
|
||||
|
||||
items.push(json!({
|
||||
"provider_id": provider.id,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::gateway::handlers::decrypt_catalog_secret_with_fallbacks;
|
||||
use crate::gateway::AppState;
|
||||
use crate::handlers::decrypt_catalog_secret_with_fallbacks;
|
||||
use crate::AppState;
|
||||
use aether_data::repository::provider_catalog::StoredProviderCatalogKey;
|
||||
|
||||
fn admin_pool_reason_indicates_ban(reason: &str) -> bool {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::gateway::{AppState, GatewayControlDecision, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayControlDecision;
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::body::{Body, Bytes};
|
||||
use axum::http::Response;
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use super::super::build_admin_batch_assign_global_models_payload;
|
||||
use crate::gateway::handlers::{
|
||||
use crate::control::GatewayControlDecision;
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::{
|
||||
admin_provider_assign_global_models_path, AdminBatchAssignGlobalModelsRequest,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayControlDecision, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
http,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use super::super::build_admin_provider_available_source_models_payload;
|
||||
use crate::gateway::handlers::admin_provider_available_source_models_path;
|
||||
use crate::gateway::{AppState, GatewayControlDecision, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayControlDecision;
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::admin_provider_available_source_models_path;
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
http,
|
||||
|
||||
@@ -2,8 +2,10 @@ use super::super::{
|
||||
admin_provider_model_name_exists, build_admin_provider_model_create_record,
|
||||
build_admin_provider_model_response,
|
||||
};
|
||||
use crate::gateway::handlers::{admin_provider_models_batch_path, AdminProviderModelCreateRequest};
|
||||
use crate::gateway::{AppState, GatewayControlDecision, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayControlDecision;
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::{admin_provider_models_batch_path, AdminProviderModelCreateRequest};
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
http,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use super::super::{build_admin_provider_model_create_record, build_admin_provider_model_response};
|
||||
use crate::gateway::handlers::{
|
||||
use crate::control::GatewayControlDecision;
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::{
|
||||
admin_provider_id_for_models_list, AdminProviderModelCreateRequest,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayControlDecision, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
http,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use crate::gateway::handlers::admin_provider_model_route_parts;
|
||||
use crate::gateway::{AppState, GatewayControlDecision, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayControlDecision;
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::admin_provider_model_route_parts;
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
http,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use super::super::build_admin_provider_model_payload;
|
||||
use crate::gateway::handlers::admin_provider_model_route_parts;
|
||||
use crate::gateway::{AppState, GatewayControlDecision, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayControlDecision;
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::admin_provider_model_route_parts;
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
http,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use super::super::build_admin_import_provider_models_payload;
|
||||
use crate::gateway::handlers::{
|
||||
use crate::control::GatewayControlDecision;
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::{
|
||||
admin_provider_import_models_path, AdminImportProviderModelsRequest,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayControlDecision, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
http,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use super::super::build_admin_provider_models_payload;
|
||||
use crate::gateway::handlers::{
|
||||
use crate::control::GatewayControlDecision;
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::{
|
||||
admin_provider_id_for_models_list, query_param_optional_bool, query_param_value,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayControlDecision, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
http,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use super::super::{build_admin_provider_model_response, build_admin_provider_model_update_record};
|
||||
use crate::gateway::handlers::{admin_provider_model_route_parts, AdminProviderModelUpdateRequest};
|
||||
use crate::gateway::{AppState, GatewayControlDecision, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayControlDecision;
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::{admin_provider_model_route_parts, AdminProviderModelUpdateRequest};
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
http,
|
||||
|
||||
@@ -2,7 +2,16 @@ use super::provider_oauth_state::{
|
||||
build_admin_provider_oauth_backend_unavailable_response,
|
||||
build_admin_provider_oauth_supported_types_payload,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::admin::misc_helpers::attach_admin_audit_response;
|
||||
use crate::handlers::{
|
||||
admin_provider_oauth_batch_import_provider_id,
|
||||
admin_provider_oauth_batch_import_task_provider_id, admin_provider_oauth_complete_key_id,
|
||||
admin_provider_oauth_complete_provider_id, admin_provider_oauth_device_authorize_provider_id,
|
||||
admin_provider_oauth_import_provider_id, admin_provider_oauth_refresh_key_id,
|
||||
admin_provider_oauth_start_key_id, admin_provider_oauth_start_provider_id,
|
||||
};
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
http,
|
||||
@@ -50,16 +59,28 @@ pub(crate) async fn maybe_build_local_admin_provider_oauth_response(
|
||||
}
|
||||
|
||||
if route_kind == Some("start_key_oauth") && *method == http::Method::POST {
|
||||
return Ok(Some(
|
||||
dispatch_start::handle_admin_provider_oauth_start_key(state, request_context).await?,
|
||||
));
|
||||
let response =
|
||||
dispatch_start::handle_admin_provider_oauth_start_key(state, request_context).await?;
|
||||
return Ok(Some(attach_admin_provider_oauth_audit_response(
|
||||
response,
|
||||
"admin_provider_oauth_authorization_started",
|
||||
"start_provider_oauth_for_key",
|
||||
"provider_key",
|
||||
admin_provider_oauth_start_key_id(&request_context.request_path),
|
||||
)));
|
||||
}
|
||||
|
||||
if route_kind == Some("start_provider_oauth") && *method == http::Method::POST {
|
||||
return Ok(Some(
|
||||
let response =
|
||||
dispatch_start::handle_admin_provider_oauth_start_provider(state, request_context)
|
||||
.await?,
|
||||
));
|
||||
.await?;
|
||||
return Ok(Some(attach_admin_provider_oauth_audit_response(
|
||||
response,
|
||||
"admin_provider_oauth_authorization_started",
|
||||
"start_provider_oauth_for_provider",
|
||||
"provider",
|
||||
admin_provider_oauth_start_provider_id(&request_context.request_path),
|
||||
)));
|
||||
}
|
||||
|
||||
if route_kind == Some("get_batch_import_task_status") && *method == http::Method::GET {
|
||||
@@ -73,76 +94,112 @@ pub(crate) async fn maybe_build_local_admin_provider_oauth_response(
|
||||
}
|
||||
|
||||
if route_kind == Some("complete_key_oauth") && *method == http::Method::POST {
|
||||
return Ok(Some(
|
||||
dispatch_complete::handle_admin_provider_oauth_complete_key(
|
||||
state,
|
||||
request_context,
|
||||
request_body,
|
||||
)
|
||||
.await?,
|
||||
));
|
||||
let response = dispatch_complete::handle_admin_provider_oauth_complete_key(
|
||||
state,
|
||||
request_context,
|
||||
request_body,
|
||||
)
|
||||
.await?;
|
||||
return Ok(Some(attach_admin_provider_oauth_audit_response(
|
||||
response,
|
||||
"admin_provider_oauth_completed",
|
||||
"complete_provider_oauth_for_key",
|
||||
"provider_key",
|
||||
admin_provider_oauth_complete_key_id(&request_context.request_path),
|
||||
)));
|
||||
}
|
||||
|
||||
if route_kind == Some("refresh_key_oauth") && *method == http::Method::POST {
|
||||
return Ok(Some(
|
||||
let response =
|
||||
dispatch_refresh::handle_admin_provider_oauth_refresh_key(state, request_context)
|
||||
.await?,
|
||||
));
|
||||
.await?;
|
||||
return Ok(Some(attach_admin_provider_oauth_audit_response(
|
||||
response,
|
||||
"admin_provider_oauth_refreshed",
|
||||
"refresh_provider_oauth_for_key",
|
||||
"provider_key",
|
||||
admin_provider_oauth_refresh_key_id(&request_context.request_path),
|
||||
)));
|
||||
}
|
||||
|
||||
if route_kind == Some("complete_provider_oauth") && *method == http::Method::POST {
|
||||
return Ok(Some(
|
||||
dispatch_complete::handle_admin_provider_oauth_complete_provider(
|
||||
state,
|
||||
request_context,
|
||||
request_body,
|
||||
)
|
||||
.await?,
|
||||
));
|
||||
let response = dispatch_complete::handle_admin_provider_oauth_complete_provider(
|
||||
state,
|
||||
request_context,
|
||||
request_body,
|
||||
)
|
||||
.await?;
|
||||
return Ok(Some(attach_admin_provider_oauth_audit_response(
|
||||
response,
|
||||
"admin_provider_oauth_completed",
|
||||
"complete_provider_oauth_for_provider",
|
||||
"provider",
|
||||
admin_provider_oauth_complete_provider_id(&request_context.request_path),
|
||||
)));
|
||||
}
|
||||
|
||||
if route_kind == Some("import_refresh_token") && *method == http::Method::POST {
|
||||
return Ok(Some(
|
||||
dispatch_import::handle_admin_provider_oauth_import_refresh_token(
|
||||
state,
|
||||
request_context,
|
||||
request_body,
|
||||
)
|
||||
.await?,
|
||||
));
|
||||
let response = dispatch_import::handle_admin_provider_oauth_import_refresh_token(
|
||||
state,
|
||||
request_context,
|
||||
request_body,
|
||||
)
|
||||
.await?;
|
||||
return Ok(Some(attach_admin_provider_oauth_audit_response(
|
||||
response,
|
||||
"admin_provider_oauth_refresh_token_imported",
|
||||
"import_provider_oauth_refresh_token",
|
||||
"provider",
|
||||
admin_provider_oauth_import_provider_id(&request_context.request_path),
|
||||
)));
|
||||
}
|
||||
|
||||
if route_kind == Some("batch_import_oauth") && *method == http::Method::POST {
|
||||
return Ok(Some(
|
||||
dispatch_batch::handle_admin_provider_oauth_batch_import(
|
||||
state,
|
||||
request_context,
|
||||
request_body,
|
||||
)
|
||||
.await?,
|
||||
));
|
||||
let response = dispatch_batch::handle_admin_provider_oauth_batch_import(
|
||||
state,
|
||||
request_context,
|
||||
request_body,
|
||||
)
|
||||
.await?;
|
||||
return Ok(Some(attach_admin_provider_oauth_audit_response(
|
||||
response,
|
||||
"admin_provider_oauth_batch_import_completed",
|
||||
"batch_import_provider_oauth",
|
||||
"provider",
|
||||
admin_provider_oauth_batch_import_provider_id(&request_context.request_path),
|
||||
)));
|
||||
}
|
||||
|
||||
if route_kind == Some("start_batch_import_oauth_task") && *method == http::Method::POST {
|
||||
return Ok(Some(
|
||||
dispatch_batch::handle_admin_provider_oauth_start_batch_import_task(
|
||||
state,
|
||||
request_context,
|
||||
request_body,
|
||||
)
|
||||
.await?,
|
||||
));
|
||||
let response = dispatch_batch::handle_admin_provider_oauth_start_batch_import_task(
|
||||
state,
|
||||
request_context,
|
||||
request_body,
|
||||
)
|
||||
.await?;
|
||||
return Ok(Some(attach_admin_provider_oauth_audit_response(
|
||||
response,
|
||||
"admin_provider_oauth_batch_import_started",
|
||||
"start_provider_oauth_batch_import",
|
||||
"provider",
|
||||
admin_provider_oauth_batch_import_task_provider_id(&request_context.request_path),
|
||||
)));
|
||||
}
|
||||
|
||||
if route_kind == Some("device_authorize") && *method == http::Method::POST {
|
||||
return Ok(Some(
|
||||
dispatch_device::handle_admin_provider_oauth_device_authorize(
|
||||
state,
|
||||
request_context,
|
||||
request_body,
|
||||
)
|
||||
.await?,
|
||||
));
|
||||
let response = dispatch_device::handle_admin_provider_oauth_device_authorize(
|
||||
state,
|
||||
request_context,
|
||||
request_body,
|
||||
)
|
||||
.await?;
|
||||
return Ok(Some(attach_admin_provider_oauth_audit_response(
|
||||
response,
|
||||
"admin_provider_oauth_device_authorization_started",
|
||||
"start_provider_oauth_device_authorization",
|
||||
"provider",
|
||||
admin_provider_oauth_device_authorize_provider_id(&request_context.request_path),
|
||||
)));
|
||||
}
|
||||
|
||||
if route_kind == Some("device_poll") && *method == http::Method::POST {
|
||||
@@ -167,3 +224,19 @@ pub(crate) async fn maybe_build_local_admin_provider_oauth_response(
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn attach_admin_provider_oauth_audit_response(
|
||||
response: Response<Body>,
|
||||
event_name: &'static str,
|
||||
action: &'static str,
|
||||
target_type: &'static str,
|
||||
target_id: Option<String>,
|
||||
) -> Response<Body> {
|
||||
if !response.status().is_success() {
|
||||
return response;
|
||||
}
|
||||
let Some(target_id) = target_id else {
|
||||
return response;
|
||||
};
|
||||
attach_admin_audit_response(response, event_name, action, target_type, &target_id)
|
||||
}
|
||||
|
||||
@@ -9,12 +9,13 @@ use super::super::provider_oauth_state::{
|
||||
current_unix_secs, decode_jwt_claims, exchange_admin_provider_oauth_refresh_token,
|
||||
is_fixed_provider_type_for_provider_oauth, save_provider_oauth_batch_task_payload,
|
||||
};
|
||||
use crate::gateway::handlers::{
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::{
|
||||
admin_provider_oauth_batch_import_provider_id,
|
||||
admin_provider_oauth_batch_import_task_provider_id,
|
||||
ADMIN_PROVIDER_OAUTH_DATA_UNAVAILABLE_DETAIL,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::{to_bytes, Body, Bytes},
|
||||
http,
|
||||
@@ -420,7 +421,7 @@ async fn execute_admin_provider_oauth_kiro_batch_import(
|
||||
.await?;
|
||||
let api_formats = provider_oauth_active_api_formats(&endpoints);
|
||||
let key_proxy = provider_oauth_key_proxy_value(proxy_node_id);
|
||||
let adapter = crate::gateway::provider_transport::KiroOAuthRefreshAdapter::default()
|
||||
let adapter = crate::provider_transport::kiro::KiroOAuthRefreshAdapter::default()
|
||||
.with_refresh_base_urls(
|
||||
admin_provider_oauth_kiro_refresh_base_url_override(state, "kiro_social_refresh"),
|
||||
admin_provider_oauth_kiro_refresh_base_url_override(state, "kiro_idc_refresh"),
|
||||
@@ -431,7 +432,7 @@ async fn execute_admin_provider_oauth_kiro_batch_import(
|
||||
|
||||
for (index, entry) in entries.iter().enumerate() {
|
||||
let Some(mut refreshed_auth_config) =
|
||||
crate::gateway::provider_transport::KiroAuthConfig::from_json_value(entry)
|
||||
crate::provider_transport::kiro::KiroAuthConfig::from_json_value(entry)
|
||||
else {
|
||||
failed += 1;
|
||||
results.push(json!({
|
||||
|
||||
@@ -11,11 +11,12 @@ use super::super::provider_oauth_state::{
|
||||
exchange_admin_provider_oauth_code, is_fixed_provider_type_for_provider_oauth,
|
||||
json_non_empty_string, json_u64_value, parse_provider_oauth_callback_params,
|
||||
};
|
||||
use crate::gateway::handlers::{
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::{
|
||||
admin_provider_oauth_complete_key_id, admin_provider_oauth_complete_provider_id,
|
||||
encrypt_catalog_secret_with_fallbacks,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
http,
|
||||
|
||||
@@ -11,12 +11,16 @@ use super::super::provider_oauth_state::{
|
||||
json_u64_value, normalize_kiro_device_region, poll_admin_kiro_device_token,
|
||||
read_provider_oauth_device_session, register_admin_kiro_device_oidc_client,
|
||||
save_provider_oauth_device_session, start_admin_kiro_device_authorization,
|
||||
StoredAdminProviderOAuthDeviceSession, KIRO_DEVICE_AUTH_SESSION_TTL_BUFFER_SECS,
|
||||
};
|
||||
use crate::gateway::handlers::{
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::admin::misc_helpers::attach_admin_audit_response;
|
||||
use crate::handlers::{
|
||||
admin_provider_oauth_device_authorize_provider_id, admin_provider_oauth_device_poll_provider_id,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::{AppState, GatewayError};
|
||||
use aether_data::repository::provider_oauth::{
|
||||
StoredAdminProviderOAuthDeviceSession, KIRO_DEVICE_AUTH_SESSION_TTL_BUFFER_SECS,
|
||||
};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
http,
|
||||
@@ -295,12 +299,16 @@ pub(super) async fn handle_admin_provider_oauth_device_poll(
|
||||
session.status = "expired".to_string();
|
||||
session.error_msg = Some("设备码已过期".to_string());
|
||||
let _ = save_provider_oauth_device_session(state, session_id, &session, 30).await;
|
||||
return Ok(Json(json!({
|
||||
"status": "expired",
|
||||
"error": "设备码已过期",
|
||||
"replaced": false,
|
||||
}))
|
||||
.into_response());
|
||||
return Ok(attach_admin_provider_oauth_device_poll_terminal_response(
|
||||
session_id,
|
||||
"expired",
|
||||
Json(json!({
|
||||
"status": "expired",
|
||||
"error": "设备码已过期",
|
||||
"replaced": false,
|
||||
}))
|
||||
.into_response(),
|
||||
));
|
||||
}
|
||||
|
||||
let Some(provider) = state
|
||||
@@ -344,23 +352,31 @@ pub(super) async fn handle_admin_provider_oauth_device_poll(
|
||||
session.status = "expired".to_string();
|
||||
session.error_msg = Some("设备码已过期".to_string());
|
||||
let _ = save_provider_oauth_device_session(state, session_id, &session, 30).await;
|
||||
return Ok(Json(json!({
|
||||
"status": "expired",
|
||||
"error": "设备码已过期",
|
||||
"replaced": false,
|
||||
}))
|
||||
.into_response());
|
||||
return Ok(attach_admin_provider_oauth_device_poll_terminal_response(
|
||||
session_id,
|
||||
"expired",
|
||||
Json(json!({
|
||||
"status": "expired",
|
||||
"error": "设备码已过期",
|
||||
"replaced": false,
|
||||
}))
|
||||
.into_response(),
|
||||
));
|
||||
}
|
||||
if error_code == "access_denied" {
|
||||
session.status = "error".to_string();
|
||||
session.error_msg = Some("用户拒绝授权".to_string());
|
||||
let _ = save_provider_oauth_device_session(state, session_id, &session, 30).await;
|
||||
return Ok(Json(json!({
|
||||
"status": "error",
|
||||
"error": "用户拒绝授权",
|
||||
"replaced": false,
|
||||
}))
|
||||
.into_response());
|
||||
return Ok(attach_admin_provider_oauth_device_poll_terminal_response(
|
||||
session_id,
|
||||
"error",
|
||||
Json(json!({
|
||||
"status": "error",
|
||||
"error": "用户拒绝授权",
|
||||
"replaced": false,
|
||||
}))
|
||||
.into_response(),
|
||||
));
|
||||
}
|
||||
let error_message = json_non_empty_string(token_result.get("error_description"))
|
||||
.or_else(|| (!error_code.is_empty()).then_some(error_code.clone()))
|
||||
@@ -489,11 +505,46 @@ pub(super) async fn handle_admin_provider_oauth_device_poll(
|
||||
session.error_msg = None;
|
||||
let _ = save_provider_oauth_device_session(state, session_id, &session, 60).await;
|
||||
|
||||
Ok(Json(json!({
|
||||
"status": "authorized",
|
||||
"key_id": persisted_key.id,
|
||||
"email": email,
|
||||
"replaced": replaced,
|
||||
}))
|
||||
.into_response())
|
||||
Ok(attach_admin_provider_oauth_device_poll_terminal_response(
|
||||
session_id,
|
||||
"authorized",
|
||||
Json(json!({
|
||||
"status": "authorized",
|
||||
"key_id": persisted_key.id,
|
||||
"email": email,
|
||||
"replaced": replaced,
|
||||
}))
|
||||
.into_response(),
|
||||
))
|
||||
}
|
||||
|
||||
fn attach_admin_provider_oauth_device_poll_terminal_response(
|
||||
session_id: &str,
|
||||
status: &str,
|
||||
response: Response<Body>,
|
||||
) -> Response<Body> {
|
||||
match status {
|
||||
"authorized" => attach_admin_audit_response(
|
||||
response,
|
||||
"admin_provider_oauth_device_authorization_completed",
|
||||
"poll_provider_oauth_device_authorization_terminal_state",
|
||||
"provider_oauth_device_session",
|
||||
session_id,
|
||||
),
|
||||
"expired" => attach_admin_audit_response(
|
||||
response,
|
||||
"admin_provider_oauth_device_authorization_expired",
|
||||
"poll_provider_oauth_device_authorization_terminal_state",
|
||||
"provider_oauth_device_session",
|
||||
session_id,
|
||||
),
|
||||
"error" => attach_admin_audit_response(
|
||||
response,
|
||||
"admin_provider_oauth_device_authorization_failed",
|
||||
"poll_provider_oauth_device_authorization_terminal_state",
|
||||
"provider_oauth_device_session",
|
||||
session_id,
|
||||
),
|
||||
_ => response,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,9 @@ use super::super::provider_oauth_state::{
|
||||
admin_provider_oauth_template, build_admin_provider_oauth_backend_unavailable_response,
|
||||
exchange_admin_provider_oauth_refresh_token, is_fixed_provider_type_for_provider_oauth,
|
||||
};
|
||||
use crate::gateway::handlers::admin_provider_oauth_import_provider_id;
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::admin_provider_oauth_import_provider_id;
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::Body,
|
||||
http,
|
||||
|
||||
@@ -5,11 +5,12 @@ use super::super::provider_oauth_refresh::{
|
||||
refresh_provider_oauth_account_state_after_update,
|
||||
};
|
||||
use super::super::provider_oauth_state::is_fixed_provider_type_for_provider_oauth;
|
||||
use crate::gateway::handlers::{
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::{
|
||||
admin_provider_oauth_refresh_key_id, decrypt_catalog_secret_with_fallbacks,
|
||||
OAUTH_ACCOUNT_BLOCK_PREFIX, OAUTH_REFRESH_FAILED_PREFIX,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::Body,
|
||||
http,
|
||||
@@ -125,7 +126,7 @@ pub(super) async fn handle_admin_provider_oauth_refresh_key(
|
||||
"缺少 refresh_token,需要重新授权",
|
||||
));
|
||||
}
|
||||
Err(crate::gateway::provider_transport::LocalOAuthRefreshError::HttpStatus {
|
||||
Err(crate::provider_transport::LocalOAuthRefreshError::HttpStatus {
|
||||
status_code,
|
||||
body_excerpt,
|
||||
..
|
||||
@@ -164,7 +165,7 @@ pub(super) async fn handle_admin_provider_oauth_refresh_key(
|
||||
format!("Token 刷新失败:{error_reason}"),
|
||||
));
|
||||
}
|
||||
Err(crate::gateway::provider_transport::LocalOAuthRefreshError::Transport {
|
||||
Err(crate::provider_transport::LocalOAuthRefreshError::Transport {
|
||||
source,
|
||||
..
|
||||
}) => {
|
||||
@@ -173,7 +174,7 @@ pub(super) async fn handle_admin_provider_oauth_refresh_key(
|
||||
format!("Token 刷新失败:{}", source),
|
||||
));
|
||||
}
|
||||
Err(crate::gateway::provider_transport::LocalOAuthRefreshError::InvalidResponse {
|
||||
Err(crate::provider_transport::LocalOAuthRefreshError::InvalidResponse {
|
||||
message,
|
||||
..
|
||||
}) => {
|
||||
|
||||
@@ -4,10 +4,11 @@ use super::super::provider_oauth_state::{
|
||||
generate_provider_oauth_pkce_verifier, is_fixed_provider_type_for_provider_oauth,
|
||||
provider_oauth_pkce_s256, save_provider_oauth_state,
|
||||
};
|
||||
use crate::gateway::handlers::{
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::{
|
||||
admin_provider_oauth_start_key_id, admin_provider_oauth_start_provider_id,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::Body,
|
||||
http,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use super::super::provider_oauth_refresh::build_internal_control_error_response;
|
||||
use super::super::provider_oauth_state::read_provider_oauth_batch_task_payload;
|
||||
use crate::gateway::handlers::admin_provider_oauth_batch_import_task_path;
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::admin::misc_helpers::attach_admin_audit_response;
|
||||
use crate::handlers::admin_provider_oauth_batch_import_task_path;
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::Body,
|
||||
http,
|
||||
@@ -37,5 +39,27 @@ pub(super) async fn handle_admin_provider_oauth_batch_import_task_status(
|
||||
));
|
||||
}
|
||||
};
|
||||
Ok(Json(payload).into_response())
|
||||
let status = payload
|
||||
.get("status")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.map(ToOwned::to_owned)
|
||||
.unwrap_or_default();
|
||||
let response = Json(payload).into_response();
|
||||
Ok(match status.as_str() {
|
||||
"completed" => attach_admin_audit_response(
|
||||
response,
|
||||
"admin_provider_oauth_batch_task_completed_viewed",
|
||||
"view_provider_oauth_batch_task_terminal_state",
|
||||
"provider_oauth_batch_task",
|
||||
&format!("{provider_id}:{task_id}"),
|
||||
),
|
||||
"failed" => attach_admin_audit_response(
|
||||
response,
|
||||
"admin_provider_oauth_batch_task_failed_viewed",
|
||||
"view_provider_oauth_batch_task_terminal_state",
|
||||
"provider_oauth_batch_task",
|
||||
&format!("{provider_id}:{task_id}"),
|
||||
),
|
||||
_ => response,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ use super::{
|
||||
extract_execution_error_message, persist_provider_quota_refresh_state,
|
||||
quota_refresh_success_invalid_state,
|
||||
};
|
||||
use crate::gateway::handlers::ANTIGRAVITY_FETCH_AVAILABLE_MODELS_PATH;
|
||||
use crate::gateway::{AppState, GatewayError};
|
||||
use crate::handlers::ANTIGRAVITY_FETCH_AVAILABLE_MODELS_PATH;
|
||||
use crate::{AppState, GatewayError};
|
||||
use aether_contracts::{ExecutionPlan, ExecutionResult, ExecutionTimeouts, RequestBody};
|
||||
use aether_data::repository::provider_catalog::{
|
||||
StoredProviderCatalogEndpoint, StoredProviderCatalogKey, StoredProviderCatalogProvider,
|
||||
@@ -64,21 +64,22 @@ fn parse_antigravity_usage_response(
|
||||
|
||||
async fn execute_antigravity_quota_plan(
|
||||
state: &AppState,
|
||||
transport: &crate::gateway::provider_transport::GatewayProviderTransportSnapshot,
|
||||
transport: &crate::provider_transport::GatewayProviderTransportSnapshot,
|
||||
authorization: (String, String),
|
||||
project_id: &str,
|
||||
auth: &crate::gateway::provider_transport::AntigravityRequestAuthSupport,
|
||||
auth: &crate::provider_transport::antigravity::AntigravityRequestAuthSupport,
|
||||
) -> Result<Option<ExecutionResult>, GatewayError> {
|
||||
let supported_auth = match auth {
|
||||
crate::gateway::provider_transport::AntigravityRequestAuthSupport::Supported(auth) => auth,
|
||||
crate::gateway::provider_transport::AntigravityRequestAuthSupport::Unsupported(_) => {
|
||||
crate::provider_transport::antigravity::AntigravityRequestAuthSupport::Supported(auth) => auth,
|
||||
crate::provider_transport::antigravity::AntigravityRequestAuthSupport::Unsupported(_) => {
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
|
||||
let mut headers = crate::gateway::provider_transport::build_antigravity_static_identity_headers(
|
||||
supported_auth,
|
||||
);
|
||||
let mut headers =
|
||||
crate::provider_transport::antigravity::build_antigravity_static_identity_headers(
|
||||
supported_auth,
|
||||
);
|
||||
headers.insert("authorization".to_string(), authorization.1);
|
||||
headers.insert("content-type".to_string(), "application/json".to_string());
|
||||
headers.insert("accept".to_string(), "application/json".to_string());
|
||||
@@ -112,9 +113,9 @@ async fn execute_antigravity_quota_plan(
|
||||
client_api_format: "gemini:chat".to_string(),
|
||||
provider_api_format: "antigravity:fetch_available_models".to_string(),
|
||||
model_name: Some("fetchAvailableModels".to_string()),
|
||||
proxy: crate::gateway::provider_transport::resolve_transport_proxy_snapshot_with_tunnel_affinity(state, transport).await,
|
||||
tls_profile: crate::gateway::provider_transport::resolve_transport_tls_profile(transport),
|
||||
timeouts: crate::gateway::provider_transport::resolve_transport_execution_timeouts(
|
||||
proxy: crate::provider_transport::resolve_transport_proxy_snapshot_with_tunnel_affinity(state, transport).await,
|
||||
tls_profile: crate::provider_transport::resolve_transport_tls_profile(transport),
|
||||
timeouts: crate::provider_transport::resolve_transport_execution_timeouts(
|
||||
transport,
|
||||
)
|
||||
.or(Some(ExecutionTimeouts {
|
||||
@@ -159,7 +160,7 @@ pub(crate) async fn refresh_antigravity_provider_quota_locally(
|
||||
};
|
||||
|
||||
let authorization = match state.resolve_local_oauth_request_auth(&transport).await? {
|
||||
Some(crate::gateway::provider_transport::LocalResolvedOAuthRequestAuth::Header {
|
||||
Some(crate::provider_transport::LocalResolvedOAuthRequestAuth::Header {
|
||||
name,
|
||||
value,
|
||||
}) => (name, value),
|
||||
@@ -176,12 +177,14 @@ pub(crate) async fn refresh_antigravity_provider_quota_locally(
|
||||
};
|
||||
|
||||
let antigravity_auth =
|
||||
crate::gateway::provider_transport::resolve_local_antigravity_request_auth(&transport);
|
||||
crate::provider_transport::antigravity::resolve_local_antigravity_request_auth(
|
||||
&transport,
|
||||
);
|
||||
let project_id = match &antigravity_auth {
|
||||
crate::gateway::provider_transport::AntigravityRequestAuthSupport::Supported(auth) => {
|
||||
crate::provider_transport::antigravity::AntigravityRequestAuthSupport::Supported(auth) => {
|
||||
auth.project_id.clone()
|
||||
}
|
||||
crate::gateway::provider_transport::AntigravityRequestAuthSupport::Unsupported(_) => {
|
||||
crate::provider_transport::antigravity::AntigravityRequestAuthSupport::Unsupported(_) => {
|
||||
failed_count += 1;
|
||||
results.push(json!({
|
||||
"key_id": key.id,
|
||||
|
||||
@@ -4,11 +4,11 @@ use super::{
|
||||
persist_provider_quota_refresh_state, provider_auto_remove_banned_keys,
|
||||
quota_refresh_success_invalid_state, should_auto_remove_structured_reason,
|
||||
};
|
||||
use crate::gateway::handlers::{
|
||||
use crate::handlers::{
|
||||
CODEX_WHAM_USAGE_URL, OAUTH_ACCOUNT_BLOCK_PREFIX, OAUTH_EXPIRED_PREFIX,
|
||||
OAUTH_REQUEST_FAILED_PREFIX,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayError};
|
||||
use crate::{AppState, GatewayError};
|
||||
use aether_contracts::{ExecutionPlan, ExecutionResult, ExecutionTimeouts, RequestBody};
|
||||
use aether_data::repository::provider_catalog::{
|
||||
StoredProviderCatalogEndpoint, StoredProviderCatalogKey, StoredProviderCatalogProvider,
|
||||
@@ -331,7 +331,7 @@ fn codex_soft_request_failure_reason(status_code: u16, upstream_message: Option<
|
||||
}
|
||||
|
||||
fn build_codex_refresh_headers(
|
||||
transport: &crate::gateway::provider_transport::GatewayProviderTransportSnapshot,
|
||||
transport: &crate::provider_transport::GatewayProviderTransportSnapshot,
|
||||
resolved_oauth_auth: Option<(String, String)>,
|
||||
) -> Result<BTreeMap<String, String>, String> {
|
||||
let mut headers = BTreeMap::new();
|
||||
@@ -379,7 +379,7 @@ fn build_codex_refresh_headers(
|
||||
|
||||
async fn execute_codex_quota_plan(
|
||||
state: &AppState,
|
||||
transport: &crate::gateway::provider_transport::GatewayProviderTransportSnapshot,
|
||||
transport: &crate::provider_transport::GatewayProviderTransportSnapshot,
|
||||
headers: BTreeMap<String, String>,
|
||||
) -> Result<Option<ExecutionResult>, GatewayError> {
|
||||
let plan = ExecutionPlan {
|
||||
@@ -403,9 +403,9 @@ async fn execute_codex_quota_plan(
|
||||
client_api_format: "openai:cli".to_string(),
|
||||
provider_api_format: "openai:cli".to_string(),
|
||||
model_name: Some("codex-wham-usage".to_string()),
|
||||
proxy: crate::gateway::provider_transport::resolve_transport_proxy_snapshot_with_tunnel_affinity(state, transport).await,
|
||||
tls_profile: crate::gateway::provider_transport::resolve_transport_tls_profile(transport),
|
||||
timeouts: crate::gateway::provider_transport::resolve_transport_execution_timeouts(
|
||||
proxy: crate::provider_transport::resolve_transport_proxy_snapshot_with_tunnel_affinity(state, transport).await,
|
||||
tls_profile: crate::provider_transport::resolve_transport_tls_profile(transport),
|
||||
timeouts: crate::provider_transport::resolve_transport_execution_timeouts(
|
||||
transport,
|
||||
)
|
||||
.or(Some(ExecutionTimeouts {
|
||||
@@ -453,7 +453,7 @@ pub(crate) async fn refresh_codex_provider_quota_locally(
|
||||
let resolved_oauth_auth = if key.auth_type.trim().eq_ignore_ascii_case("oauth") {
|
||||
match state.resolve_local_oauth_request_auth(&transport).await? {
|
||||
Some(
|
||||
crate::gateway::provider_transport::LocalResolvedOAuthRequestAuth::Header {
|
||||
crate::provider_transport::LocalResolvedOAuthRequestAuth::Header {
|
||||
name,
|
||||
value,
|
||||
},
|
||||
|
||||
@@ -2,10 +2,10 @@ use super::{
|
||||
coerce_json_f64, execute_provider_quota_plan, extract_execution_error_message,
|
||||
persist_provider_quota_refresh_state, quota_refresh_success_invalid_state,
|
||||
};
|
||||
use crate::gateway::handlers::{
|
||||
use crate::handlers::{
|
||||
encrypt_catalog_secret_with_fallbacks, KIRO_USAGE_LIMITS_PATH, KIRO_USAGE_SDK_VERSION,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayError};
|
||||
use crate::{AppState, GatewayError};
|
||||
use aether_contracts::{ExecutionPlan, ExecutionResult, ExecutionTimeouts, RequestBody};
|
||||
use aether_data::repository::provider_catalog::{
|
||||
StoredProviderCatalogEndpoint, StoredProviderCatalogKey, StoredProviderCatalogProvider,
|
||||
@@ -167,7 +167,7 @@ fn parse_kiro_usage_response(
|
||||
}
|
||||
|
||||
fn build_kiro_usage_headers(
|
||||
auth: &crate::gateway::provider_transport::KiroRequestAuth,
|
||||
auth: &crate::provider_transport::kiro::KiroRequestAuth,
|
||||
) -> BTreeMap<String, String> {
|
||||
let kiro_version = auth.auth_config.effective_kiro_version();
|
||||
let machine_id = auth.machine_id.trim();
|
||||
@@ -200,7 +200,9 @@ fn build_kiro_usage_headers(
|
||||
])
|
||||
}
|
||||
|
||||
fn build_kiro_usage_url(auth: &crate::gateway::provider_transport::KiroRequestAuth) -> String {
|
||||
fn build_kiro_usage_url(
|
||||
auth: &crate::provider_transport::kiro::KiroRequestAuth,
|
||||
) -> String {
|
||||
let host = format!(
|
||||
"q.{}.amazonaws.com",
|
||||
auth.auth_config.effective_api_region()
|
||||
@@ -220,8 +222,8 @@ fn build_kiro_usage_url(auth: &crate::gateway::provider_transport::KiroRequestAu
|
||||
|
||||
async fn execute_kiro_quota_plan(
|
||||
state: &AppState,
|
||||
transport: &crate::gateway::provider_transport::GatewayProviderTransportSnapshot,
|
||||
auth: &crate::gateway::provider_transport::KiroRequestAuth,
|
||||
transport: &crate::provider_transport::GatewayProviderTransportSnapshot,
|
||||
auth: &crate::provider_transport::kiro::KiroRequestAuth,
|
||||
) -> Result<Option<ExecutionResult>, GatewayError> {
|
||||
let plan = ExecutionPlan {
|
||||
request_id: format!("kiro-quota:{}", transport.key.id),
|
||||
@@ -244,9 +246,9 @@ async fn execute_kiro_quota_plan(
|
||||
client_api_format: "claude:cli".to_string(),
|
||||
provider_api_format: "kiro:usage".to_string(),
|
||||
model_name: Some("kiro-usage-limits".to_string()),
|
||||
proxy: crate::gateway::provider_transport::resolve_transport_proxy_snapshot_with_tunnel_affinity(state, transport).await,
|
||||
tls_profile: crate::gateway::provider_transport::resolve_transport_tls_profile(transport),
|
||||
timeouts: crate::gateway::provider_transport::resolve_transport_execution_timeouts(
|
||||
proxy: crate::provider_transport::resolve_transport_proxy_snapshot_with_tunnel_affinity(state, transport).await,
|
||||
tls_profile: crate::provider_transport::resolve_transport_tls_profile(transport),
|
||||
timeouts: crate::provider_transport::resolve_transport_execution_timeouts(
|
||||
transport,
|
||||
)
|
||||
.or(Some(ExecutionTimeouts {
|
||||
@@ -291,7 +293,7 @@ pub(crate) async fn refresh_kiro_provider_quota_locally(
|
||||
};
|
||||
|
||||
let Some(auth) = (match state.resolve_local_oauth_request_auth(&transport).await? {
|
||||
Some(crate::gateway::provider_transport::LocalResolvedOAuthRequestAuth::Kiro(auth)) => {
|
||||
Some(crate::provider_transport::LocalResolvedOAuthRequestAuth::Kiro(auth)) => {
|
||||
Some(auth)
|
||||
}
|
||||
_ => None,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::gateway::handlers::{OAUTH_ACCOUNT_BLOCK_PREFIX, OAUTH_REFRESH_FAILED_PREFIX};
|
||||
use crate::gateway::{AppState, GatewayError};
|
||||
use crate::handlers::{OAUTH_ACCOUNT_BLOCK_PREFIX, OAUTH_REFRESH_FAILED_PREFIX};
|
||||
use crate::{AppState, GatewayError};
|
||||
use aether_contracts::{ExecutionPlan, ExecutionResult};
|
||||
use aether_data::repository::provider_catalog::StoredProviderCatalogKey;
|
||||
use std::collections::BTreeSet;
|
||||
@@ -176,11 +176,13 @@ pub(crate) async fn persist_provider_quota_refresh_state(
|
||||
|
||||
pub(super) async fn execute_provider_quota_plan(
|
||||
state: &AppState,
|
||||
transport: &crate::gateway::provider_transport::GatewayProviderTransportSnapshot,
|
||||
transport: &crate::provider_transport::GatewayProviderTransportSnapshot,
|
||||
plan: ExecutionPlan,
|
||||
quota_kind: &str,
|
||||
) -> Result<Option<ExecutionResult>, GatewayError> {
|
||||
match crate::gateway::execute_execution_runtime_sync_plan(state, None, &plan).await {
|
||||
match crate::execution_runtime::execute_execution_runtime_sync_plan(state, None, &plan)
|
||||
.await
|
||||
{
|
||||
Ok(result) => Ok(Some(result)),
|
||||
Err(err) => {
|
||||
warn!(
|
||||
|
||||
@@ -5,12 +5,12 @@ use super::provider_oauth_quota::{
|
||||
use super::provider_oauth_state::{
|
||||
enrich_admin_provider_oauth_auth_config, json_non_empty_string, json_u64_value,
|
||||
};
|
||||
use crate::gateway::handlers::{
|
||||
use crate::handlers::{
|
||||
decrypt_catalog_secret_with_fallbacks, encrypt_catalog_secret_with_fallbacks,
|
||||
parse_catalog_auth_config_json, OAUTH_ACCOUNT_BLOCK_PREFIX, OAUTH_EXPIRED_PREFIX,
|
||||
OAUTH_REFRESH_FAILED_PREFIX, OAUTH_REQUEST_FAILED_PREFIX,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayError};
|
||||
use crate::{AppState, GatewayError};
|
||||
use aether_data::repository::provider_catalog::{
|
||||
StoredProviderCatalogEndpoint, StoredProviderCatalogKey, StoredProviderCatalogProvider,
|
||||
};
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
use super::{
|
||||
build_internal_control_error_response, normalize_provider_oauth_refresh_error_message,
|
||||
};
|
||||
use crate::gateway::handlers::ADMIN_PROVIDER_OAUTH_DATA_UNAVAILABLE_DETAIL;
|
||||
use crate::gateway::provider_transport::{
|
||||
use crate::handlers::ADMIN_PROVIDER_OAUTH_DATA_UNAVAILABLE_DETAIL;
|
||||
use crate::provider_transport::provider_types::{
|
||||
provider_type_admin_oauth_template, provider_type_is_fixed_for_admin_oauth,
|
||||
ProviderOAuthTemplate, ADMIN_PROVIDER_OAUTH_TEMPLATE_TYPES,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayError};
|
||||
use crate::{AppState, GatewayError};
|
||||
use aether_data::repository::provider_oauth::{
|
||||
build_provider_oauth_batch_task_status_payload, provider_oauth_batch_task_storage_key,
|
||||
provider_oauth_device_session_storage_key, provider_oauth_state_storage_key,
|
||||
StoredAdminProviderOAuthDeviceSession, StoredAdminProviderOAuthState,
|
||||
PROVIDER_OAUTH_BATCH_TASK_TTL_SECS, PROVIDER_OAUTH_STATE_TTL_SECS,
|
||||
};
|
||||
use axum::{body::Body, http, response::Response};
|
||||
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::collections::BTreeMap;
|
||||
@@ -50,32 +55,11 @@ pub(crate) fn build_admin_provider_oauth_backend_unavailable_response() -> Respo
|
||||
)
|
||||
}
|
||||
|
||||
const KIRO_DEVICE_AUTH_SESSION_PREFIX: &str = "device_auth_session:";
|
||||
pub(crate) const KIRO_DEVICE_AUTH_SESSION_TTL_BUFFER_SECS: u64 = 60;
|
||||
const PROVIDER_OAUTH_BATCH_TASK_TTL_SECS: u64 = 24 * 60 * 60;
|
||||
const KIRO_DEVICE_DEFAULT_START_URL: &str = "https://view.awsapps.com/start";
|
||||
const KIRO_DEVICE_DEFAULT_REGION: &str = "us-east-1";
|
||||
const KIRO_IDC_AMZ_USER_AGENT: &str =
|
||||
"aws-sdk-js/3.738.0 ua/2.1 os/other lang/js md/browser#unknown_unknown api/sso-oidc#3.738.0 m/E KiroIDE";
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, Deserialize)]
|
||||
pub(crate) struct StoredAdminProviderOAuthDeviceSession {
|
||||
pub(crate) provider_id: String,
|
||||
pub(crate) region: String,
|
||||
pub(crate) client_id: String,
|
||||
pub(crate) client_secret: String,
|
||||
pub(crate) device_code: String,
|
||||
pub(crate) interval: u64,
|
||||
pub(crate) expires_at_unix_secs: u64,
|
||||
pub(crate) status: String,
|
||||
pub(crate) proxy_node_id: Option<String>,
|
||||
pub(crate) created_at_unix_secs: u64,
|
||||
pub(crate) key_id: Option<String>,
|
||||
pub(crate) email: Option<String>,
|
||||
pub(crate) replaced: bool,
|
||||
pub(crate) error_msg: Option<String>,
|
||||
}
|
||||
|
||||
pub(crate) fn default_kiro_device_start_url() -> String {
|
||||
KIRO_DEVICE_DEFAULT_START_URL.to_string()
|
||||
}
|
||||
@@ -95,10 +79,6 @@ pub(crate) fn normalize_kiro_device_region(value: Option<&str>) -> Option<String
|
||||
.then(|| value.to_string())
|
||||
}
|
||||
|
||||
fn provider_oauth_device_session_key(session_id: &str) -> String {
|
||||
format!("{KIRO_DEVICE_AUTH_SESSION_PREFIX}{session_id}")
|
||||
}
|
||||
|
||||
pub(crate) fn current_unix_secs() -> u64 {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
@@ -113,7 +93,7 @@ pub(crate) async fn save_provider_oauth_device_session(
|
||||
session: &StoredAdminProviderOAuthDeviceSession,
|
||||
ttl_seconds: u64,
|
||||
) -> Result<(), Response<Body>> {
|
||||
let key = provider_oauth_device_session_key(session_id);
|
||||
let key = provider_oauth_device_session_storage_key(session_id);
|
||||
let value = serde_json::to_string(session).map_err(|_| {
|
||||
build_internal_control_error_response(
|
||||
http::StatusCode::SERVICE_UNAVAILABLE,
|
||||
@@ -145,7 +125,7 @@ pub(crate) async fn read_provider_oauth_device_session(
|
||||
state: &AppState,
|
||||
session_id: &str,
|
||||
) -> Result<Option<StoredAdminProviderOAuthDeviceSession>, GatewayError> {
|
||||
let key = provider_oauth_device_session_key(session_id);
|
||||
let key = provider_oauth_device_session_storage_key(session_id);
|
||||
let raw = if let Some(runner) = state.redis_kv_runner() {
|
||||
let mut connection = runner
|
||||
.client()
|
||||
@@ -330,14 +310,6 @@ pub(crate) fn build_kiro_device_key_name(
|
||||
format!("kiro_{fallback} (idc)")
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub(crate) struct StoredAdminProviderOAuthState {
|
||||
pub(crate) key_id: String,
|
||||
pub(crate) provider_id: String,
|
||||
pub(crate) provider_type: String,
|
||||
pub(crate) pkce_verifier: Option<String>,
|
||||
}
|
||||
|
||||
pub(crate) fn generate_provider_oauth_nonce() -> String {
|
||||
format!("{}{}", Uuid::new_v4().simple(), Uuid::new_v4().simple())
|
||||
}
|
||||
@@ -375,11 +347,11 @@ pub(crate) async fn save_provider_oauth_state(
|
||||
.map(|duration| duration.as_secs())
|
||||
.unwrap_or(0),
|
||||
});
|
||||
let key = format!("provider_oauth_state:{nonce}");
|
||||
let key = provider_oauth_state_storage_key(&nonce);
|
||||
let value = payload.to_string();
|
||||
if let Some(runner) = state.redis_kv_runner() {
|
||||
runner
|
||||
.setex(&key, &value, Some(600))
|
||||
.setex(&key, &value, Some(PROVIDER_OAUTH_STATE_TTL_SECS))
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))?;
|
||||
return Ok(nonce);
|
||||
@@ -422,7 +394,7 @@ pub(crate) async fn consume_provider_oauth_state(
|
||||
state: &AppState,
|
||||
nonce: &str,
|
||||
) -> Result<Option<StoredAdminProviderOAuthState>, GatewayError> {
|
||||
let key = format!("provider_oauth_state:{nonce}");
|
||||
let key = provider_oauth_state_storage_key(nonce);
|
||||
let raw = if let Some(runner) = state.redis_kv_runner() {
|
||||
let mut connection = runner
|
||||
.client()
|
||||
@@ -731,83 +703,12 @@ pub(crate) fn build_provider_oauth_start_response(
|
||||
})
|
||||
}
|
||||
|
||||
fn build_provider_oauth_batch_task_status_payload(
|
||||
provider_id: &str,
|
||||
state: &serde_json::Map<String, serde_json::Value>,
|
||||
) -> serde_json::Value {
|
||||
let now_unix_secs = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map(|duration| duration.as_secs())
|
||||
.unwrap_or(0);
|
||||
let raw_status = state
|
||||
.get("status")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.unwrap_or("failed");
|
||||
let normalized_status = match raw_status {
|
||||
"submitted" | "processing" | "completed" | "failed" => raw_status,
|
||||
_ => "failed",
|
||||
};
|
||||
let error_samples = state
|
||||
.get("error_samples")
|
||||
.and_then(serde_json::Value::as_array)
|
||||
.map(|items| {
|
||||
items
|
||||
.iter()
|
||||
.filter(|item| item.is_object())
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
json!({
|
||||
"task_id": state
|
||||
.get("task_id")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.unwrap_or_default(),
|
||||
"provider_id": provider_id,
|
||||
"provider_type": state
|
||||
.get("provider_type")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.unwrap_or_default(),
|
||||
"status": normalized_status,
|
||||
"total": state.get("total").and_then(serde_json::Value::as_i64).unwrap_or(0),
|
||||
"processed": state.get("processed").and_then(serde_json::Value::as_i64).unwrap_or(0),
|
||||
"success": state.get("success").and_then(serde_json::Value::as_i64).unwrap_or(0),
|
||||
"failed": state.get("failed").and_then(serde_json::Value::as_i64).unwrap_or(0),
|
||||
"progress_percent": state
|
||||
.get("progress_percent")
|
||||
.and_then(serde_json::Value::as_i64)
|
||||
.unwrap_or(0)
|
||||
.clamp(0, 100),
|
||||
"message": state.get("message").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"error": state.get("error").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"error_samples": error_samples,
|
||||
"created_at": state
|
||||
.get("created_at")
|
||||
.and_then(serde_json::Value::as_u64)
|
||||
.unwrap_or(now_unix_secs),
|
||||
"started_at": state.get("started_at").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"finished_at": state
|
||||
.get("finished_at")
|
||||
.cloned()
|
||||
.unwrap_or(serde_json::Value::Null),
|
||||
"updated_at": state
|
||||
.get("updated_at")
|
||||
.and_then(serde_json::Value::as_u64)
|
||||
.unwrap_or(now_unix_secs),
|
||||
})
|
||||
}
|
||||
|
||||
fn provider_oauth_batch_task_key(task_id: &str) -> String {
|
||||
format!("provider_oauth_batch_task:{task_id}")
|
||||
}
|
||||
|
||||
pub(crate) async fn save_provider_oauth_batch_task_payload(
|
||||
state: &AppState,
|
||||
task_id: &str,
|
||||
task_state: &serde_json::Value,
|
||||
) -> Result<(), GatewayError> {
|
||||
let key = provider_oauth_batch_task_key(task_id);
|
||||
let key = provider_oauth_batch_task_storage_key(task_id);
|
||||
let serialized =
|
||||
serde_json::to_string(task_state).map_err(|err| GatewayError::Internal(err.to_string()))?;
|
||||
|
||||
@@ -843,7 +744,7 @@ pub(crate) async fn read_provider_oauth_batch_task_payload(
|
||||
provider_id: &str,
|
||||
task_id: &str,
|
||||
) -> Result<Option<serde_json::Value>, GatewayError> {
|
||||
let key = provider_oauth_batch_task_key(task_id);
|
||||
let key = provider_oauth_batch_task_storage_key(task_id);
|
||||
let raw = if let Some(runner) = state.redis_kv_runner() {
|
||||
let Ok(mut connection) = runner.client().get_multiplexed_async_connection().await else {
|
||||
return Err(GatewayError::Internal(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::body::{Body, Bytes};
|
||||
use axum::http::Response;
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::gateway::handlers::{
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::{
|
||||
admin_provider_ops_architecture_id_from_path, is_admin_provider_ops_architectures_root,
|
||||
};
|
||||
use crate::gateway::{GatewayError, GatewayPublicRequestContext};
|
||||
use crate::GatewayError;
|
||||
use axum::{
|
||||
body::Body,
|
||||
http,
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use crate::gateway::handlers::{
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::{
|
||||
admin_provider_id_for_provider_ops_balance, admin_provider_id_for_provider_ops_checkin,
|
||||
admin_provider_id_for_provider_ops_config, admin_provider_id_for_provider_ops_connect,
|
||||
admin_provider_id_for_provider_ops_disconnect, admin_provider_id_for_provider_ops_status,
|
||||
admin_provider_id_for_provider_ops_verify, admin_provider_ops_action_route_parts,
|
||||
decrypt_catalog_secret_with_fallbacks, encrypt_catalog_secret_with_fallbacks,
|
||||
};
|
||||
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
|
||||
use crate::{AppState, GatewayError};
|
||||
use aether_data::repository::provider_catalog::{
|
||||
StoredProviderCatalogEndpoint, StoredProviderCatalogProvider,
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ use super::{
|
||||
resolve_admin_provider_ops_base_url, AdminProviderOpsCheckinOutcome,
|
||||
ADMIN_PROVIDER_OPS_ACTION_RUST_ONLY_MESSAGE,
|
||||
};
|
||||
use crate::gateway::AppState;
|
||||
use crate::AppState;
|
||||
use aether_data::repository::provider_catalog::{
|
||||
StoredProviderCatalogEndpoint, StoredProviderCatalogProvider,
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use super::{AdminProviderOpsSaveConfigRequest, ADMIN_PROVIDER_OPS_SENSITIVE_FIELDS};
|
||||
use crate::gateway::handlers::{
|
||||
use crate::handlers::{
|
||||
decrypt_catalog_secret_with_fallbacks, encrypt_catalog_secret_with_fallbacks,
|
||||
};
|
||||
use crate::gateway::AppState;
|
||||
use crate::AppState;
|
||||
use aether_data::repository::provider_catalog::{
|
||||
StoredProviderCatalogEndpoint, StoredProviderCatalogProvider,
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user