feat: complete user login admin keys and deployment docs

This commit is contained in:
boos
2026-05-19 10:06:52 +02:00
parent 812a7c6c07
commit 4fe84707e5
16 changed files with 1384 additions and 187 deletions
+81 -22
View File
@@ -161,33 +161,66 @@ async fn delete_api_key(
Ok(StatusCode::NO_CONTENT)
}
#[derive(Debug, Deserialize)]
struct UserLogsQuery {
q: Option<String>,
status: Option<String>,
date_from: Option<String>,
date_to: Option<String>,
page: Option<i64>,
page_size: Option<i64>,
// legacy compat
limit: Option<i64>,
offset: Option<i64>,
}
async fn get_user_logs(
State(state): State<AppState>,
headers: HeaderMap,
Query(params): Query<std::collections::HashMap<String, String>>,
Query(params): Query<UserLogsQuery>,
) -> AppResult<Json<Value>> {
let user_id = current_user_id(&headers, &state)?;
let limit = params
.get("limit")
.and_then(|v| v.parse::<i64>().ok())
let page_size = params
.page_size
.or(params.limit)
.unwrap_or(20)
.clamp(1, 100);
let offset = params
.get("offset")
.and_then(|v| v.parse::<i64>().ok())
.unwrap_or(0)
.max(0);
.clamp(1, 200);
let page = params.page.unwrap_or(1).max(1);
let offset = params.offset.unwrap_or((page - 1) * page_size).max(0);
let q = params.q.as_deref().map(str::trim).filter(|s| !s.is_empty());
let q_like = q.map(|s| format!("%{s}%"));
let status = params
.status
.as_deref()
.filter(|s| !s.is_empty() && *s != "all");
let date_from = params.date_from.as_deref().filter(|s| !s.is_empty());
let date_to = params.date_to.as_deref().filter(|s| !s.is_empty());
let items: Vec<Value> = sqlx::query_as::<_, crate::routes::stats::ProxyLogRow>(
"SELECT l.* FROM proxy_logs l
JOIN downstream_api_keys k ON l.downstream_api_key_id = k.id
WHERE k.user_id = ?
ORDER BY l.created_at DESC
LIMIT ? OFFSET ?",
r#"
SELECT l.* FROM proxy_logs l
JOIN downstream_api_keys k ON l.downstream_api_key_id = k.id
WHERE k.user_id = ?
AND (? IS NULL OR l.model_requested LIKE ?)
AND (? IS NULL OR l.status = ?)
AND (? IS NULL OR l.created_at >= ?)
AND (? IS NULL OR l.created_at <= ?)
ORDER BY l.created_at DESC
LIMIT ? OFFSET ?
"#,
)
.bind(user_id)
.bind(limit)
.bind(q_like.as_deref())
.bind(q_like.as_deref())
.bind(status)
.bind(status)
.bind(date_from)
.bind(date_from)
.bind(date_to)
.bind(date_to)
.bind(page_size)
.bind(offset)
.fetch_all(&*state.db)
.await?
@@ -195,20 +228,46 @@ async fn get_user_logs(
.map(|row| serde_json::to_value(row).unwrap_or(json!({})))
.collect();
let total: i64 = sqlx::query_scalar(
"SELECT COUNT(*) FROM proxy_logs l
JOIN downstream_api_keys k ON l.downstream_api_key_id = k.id
WHERE k.user_id = ?",
let (total, success_count, total_cost, total_tokens): (i64, i64, f64, i64) = sqlx::query_as(
r#"
SELECT
COUNT(*),
SUM(CASE WHEN l.status = 'success' THEN 1 ELSE 0 END),
COALESCE(SUM(l.estimated_cost), 0.0),
COALESCE(SUM(l.total_tokens), 0)
FROM proxy_logs l
JOIN downstream_api_keys k ON l.downstream_api_key_id = k.id
WHERE k.user_id = ?
AND (? IS NULL OR l.model_requested LIKE ?)
AND (? IS NULL OR l.status = ?)
AND (? IS NULL OR l.created_at >= ?)
AND (? IS NULL OR l.created_at <= ?)
"#,
)
.bind(user_id)
.bind(q_like.as_deref())
.bind(q_like.as_deref())
.bind(status)
.bind(status)
.bind(date_from)
.bind(date_from)
.bind(date_to)
.bind(date_to)
.fetch_one(&*state.db)
.await?;
Ok(Json(json!({
"items": items,
"total": total,
"limit": limit,
"offset": offset
"page": page,
"page_size": page_size,
"summary": {
"total": total,
"success": success_count,
"failed": total - success_count,
"total_cost": total_cost,
"total_tokens": total_tokens,
}
})))
}