forked from boos/BoosAPI
perf: speed up app loading
This commit is contained in:
+5
-2
@@ -10,6 +10,7 @@ use include_dir::{include_dir, Dir};
|
||||
use serde_json::{json, Value};
|
||||
use std::convert::Infallible;
|
||||
use tower::service_fn;
|
||||
use tower_http::compression::CompressionLayer;
|
||||
use tower_http::services::ServeDir;
|
||||
|
||||
static EMBEDDED_WEB: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/dist/web");
|
||||
@@ -78,7 +79,7 @@ pub fn build_router(state: AppState) -> Router {
|
||||
.with_state(state);
|
||||
|
||||
let router = Router::new().merge(api_routes);
|
||||
match std::env::var("BOOSAPI_WEB_DIR")
|
||||
let router = match std::env::var("BOOSAPI_WEB_DIR")
|
||||
.ok()
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty())
|
||||
@@ -111,7 +112,9 @@ pub fn build_router(state: AppState) -> Router {
|
||||
router.fallback_service(static_service)
|
||||
}
|
||||
None => router.fallback(embedded_static_handler),
|
||||
}
|
||||
};
|
||||
|
||||
router.layer(CompressionLayer::new())
|
||||
}
|
||||
|
||||
fn api_not_found_response(path: &str) -> Response {
|
||||
|
||||
+41
-30
@@ -54,6 +54,45 @@ async fn dashboard(
|
||||
State(state): State<AppState>,
|
||||
Query(q): Query<std::collections::HashMap<String, String>>,
|
||||
) -> AppResult<Json<Value>> {
|
||||
match q.get("view").map(String::as_str) {
|
||||
Some("summary") => return Ok(Json(build_dashboard_summary(&state).await?)),
|
||||
Some("insights") => {
|
||||
let site_availability = build_site_availability(&state).await?;
|
||||
let model_analysis = build_model_analysis(&state, 7).await?;
|
||||
return Ok(Json(json!({
|
||||
"generatedAt": chrono::Utc::now().to_rfc3339(),
|
||||
"insights": [],
|
||||
"siteAvailability": site_availability,
|
||||
"modelAnalysis": model_analysis,
|
||||
})));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let summary = build_dashboard_summary(&state).await?;
|
||||
let site_availability = build_site_availability(&state).await?;
|
||||
let model_analysis = build_model_analysis(&state, 7).await?;
|
||||
|
||||
Ok(Json(json!({
|
||||
"generatedAt": summary["generatedAt"].clone(),
|
||||
"totalBalance": summary["totalBalance"].clone(),
|
||||
"totalUsed": summary["totalUsed"].clone(),
|
||||
"todaySpend": summary["todaySpend"].clone(),
|
||||
"todayReward": summary["todayReward"].clone(),
|
||||
"activeAccounts": summary["activeAccounts"].clone(),
|
||||
"totalAccounts": summary["totalAccounts"].clone(),
|
||||
"todayCheckin": summary["todayCheckin"].clone(),
|
||||
"proxy24h": summary["proxy24h"].clone(),
|
||||
"performance": summary["performance"].clone(),
|
||||
"siteAvailability": site_availability,
|
||||
"modelAnalysis": model_analysis,
|
||||
"summary": summary["summary"].clone(),
|
||||
"insights": [],
|
||||
"stats": summary["stats"].clone(),
|
||||
})))
|
||||
}
|
||||
|
||||
async fn build_dashboard_summary(state: &AppState) -> AppResult<Value> {
|
||||
let sites: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM sites")
|
||||
.fetch_one(&*state.db)
|
||||
.await?;
|
||||
@@ -134,10 +173,8 @@ async fn dashboard(
|
||||
)
|
||||
.fetch_one(&*state.db)
|
||||
.await?;
|
||||
let site_availability = build_site_availability(&state).await?;
|
||||
let model_analysis = build_model_analysis(&state, 7).await?;
|
||||
|
||||
let payload = json!({
|
||||
Ok(json!({
|
||||
"generatedAt": chrono::Utc::now().to_rfc3339(),
|
||||
"totalBalance": total_balance,
|
||||
"totalUsed": total_used,
|
||||
@@ -159,8 +196,6 @@ async fn dashboard(
|
||||
"requestsPerMinute": perf_requests as f64 * 60.0 / perf_window_secs as f64,
|
||||
"tokensPerMinute": perf_tokens as f64 * 60.0 / perf_window_secs as f64,
|
||||
},
|
||||
"siteAvailability": site_availability,
|
||||
"modelAnalysis": model_analysis,
|
||||
"summary": {
|
||||
"sites": sites,
|
||||
"accounts": accounts,
|
||||
@@ -178,31 +213,7 @@ async fn dashboard(
|
||||
"successRequests": success_24h,
|
||||
"totalCost": total_cost_24h,
|
||||
},
|
||||
});
|
||||
|
||||
match q.get("view").map(String::as_str) {
|
||||
Some("summary") => Ok(Json(json!({
|
||||
"generatedAt": payload["generatedAt"],
|
||||
"summary": payload["summary"],
|
||||
"stats": payload["stats"],
|
||||
"totalBalance": payload["totalBalance"],
|
||||
"totalUsed": payload["totalUsed"],
|
||||
"todaySpend": payload["todaySpend"],
|
||||
"todayReward": payload["todayReward"],
|
||||
"activeAccounts": payload["activeAccounts"],
|
||||
"totalAccounts": payload["totalAccounts"],
|
||||
"todayCheckin": payload["todayCheckin"],
|
||||
"proxy24h": payload["proxy24h"],
|
||||
"performance": payload["performance"],
|
||||
}))),
|
||||
Some("insights") => Ok(Json(json!({
|
||||
"generatedAt": payload["generatedAt"],
|
||||
"insights": payload["insights"],
|
||||
"siteAvailability": payload["siteAvailability"],
|
||||
"modelAnalysis": payload["modelAnalysis"],
|
||||
}))),
|
||||
_ => Ok(Json(payload)),
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
async fn build_model_analysis(state: &AppState, days: i64) -> AppResult<Value> {
|
||||
|
||||
+87
-22
@@ -29,16 +29,22 @@ pub fn routes() -> Router<AppState> {
|
||||
)
|
||||
}
|
||||
|
||||
async fn user_picture_log_items(state: &AppState, user_id: i64) -> AppResult<Vec<Value>> {
|
||||
async fn user_picture_log_items_limited(
|
||||
state: &AppState,
|
||||
user_id: i64,
|
||||
limit: i64,
|
||||
) -> AppResult<Vec<Value>> {
|
||||
let tasks: Vec<BackgroundTask> = sqlx::query_as(
|
||||
"SELECT *
|
||||
FROM background_tasks
|
||||
WHERE task_type = ?
|
||||
AND json_extract(result_json, '$.userId') = ?
|
||||
ORDER BY created_at DESC, id DESC",
|
||||
ORDER BY created_at DESC, id DESC
|
||||
LIMIT ?",
|
||||
)
|
||||
.bind("picture-agent-generation")
|
||||
.bind(user_id)
|
||||
.bind(limit.clamp(1, 5000))
|
||||
.fetch_all(&*state.db)
|
||||
.await?;
|
||||
|
||||
@@ -102,7 +108,11 @@ async fn user_picture_log_items(state: &AppState, user_id: i64) -> AppResult<Vec
|
||||
.collect())
|
||||
}
|
||||
|
||||
async fn user_usage_log_items(state: &AppState, user_id: i64) -> AppResult<Vec<Value>> {
|
||||
async fn user_usage_log_items_limited(
|
||||
state: &AppState,
|
||||
user_id: i64,
|
||||
limit: i64,
|
||||
) -> AppResult<Vec<Value>> {
|
||||
let rows = sqlx::query(
|
||||
r#"
|
||||
SELECT
|
||||
@@ -118,10 +128,11 @@ async fn user_usage_log_items(state: &AppState, user_id: i64) -> AppResult<Vec<V
|
||||
JOIN downstream_api_keys k ON k.id = l.downstream_key_id
|
||||
WHERE k.user_id = ?
|
||||
ORDER BY l.created_at DESC, l.id DESC
|
||||
LIMIT 5000
|
||||
LIMIT ?
|
||||
"#,
|
||||
)
|
||||
.bind(user_id)
|
||||
.bind(limit.clamp(1, 5000))
|
||||
.fetch_all(&*state.db)
|
||||
.await?;
|
||||
|
||||
@@ -285,6 +296,16 @@ fn sort_user_logs_desc(items: &mut [Value]) {
|
||||
}
|
||||
|
||||
async fn load_user_log_items(state: &AppState, user_id: i64) -> AppResult<Vec<Value>> {
|
||||
load_user_log_items_limited(state, user_id, 5000, false).await
|
||||
}
|
||||
|
||||
async fn load_user_log_items_limited(
|
||||
state: &AppState,
|
||||
user_id: i64,
|
||||
limit: i64,
|
||||
truncate_combined: bool,
|
||||
) -> AppResult<Vec<Value>> {
|
||||
let limit = limit.clamp(1, 5000);
|
||||
let mut items: Vec<Value> = sqlx::query_as::<_, crate::routes::stats::ProxyLogRow>(
|
||||
r#"
|
||||
SELECT
|
||||
@@ -308,22 +329,78 @@ async fn load_user_log_items(state: &AppState, user_id: i64) -> AppResult<Vec<Va
|
||||
LEFT JOIN sites s ON s.id = a.site_id
|
||||
WHERE k.user_id = ?
|
||||
ORDER BY l.created_at DESC
|
||||
LIMIT 5000
|
||||
LIMIT ?
|
||||
"#,
|
||||
)
|
||||
.bind(user_id)
|
||||
.bind(limit)
|
||||
.fetch_all(&*state.db)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|row| serde_json::to_value(row).unwrap_or(json!({})))
|
||||
.collect();
|
||||
|
||||
items.extend(user_usage_log_items(state, user_id).await?);
|
||||
items.extend(user_picture_log_items(state, user_id).await?);
|
||||
items.extend(user_usage_log_items_limited(state, user_id, limit).await?);
|
||||
items.extend(user_picture_log_items_limited(state, user_id, limit).await?);
|
||||
sort_user_logs_desc(&mut items);
|
||||
if truncate_combined {
|
||||
items.truncate(limit as usize);
|
||||
}
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
async fn load_user_log_summary(state: &AppState, user_id: i64) -> AppResult<(i64, i64, f64)> {
|
||||
let proxy_summary: (i64, i64, f64) = sqlx::query_as(
|
||||
r#"
|
||||
SELECT
|
||||
COUNT(l.id) AS total,
|
||||
COALESCE(SUM(CASE WHEN l.status = 'success' THEN 1 ELSE 0 END), 0) AS success_count,
|
||||
COALESCE(SUM(l.estimated_cost), 0.0) AS total_cost
|
||||
FROM proxy_logs l
|
||||
JOIN downstream_api_keys k ON l.downstream_api_key_id = k.id
|
||||
WHERE k.user_id = ?
|
||||
"#,
|
||||
)
|
||||
.bind(user_id)
|
||||
.fetch_one(&*state.db)
|
||||
.await?;
|
||||
|
||||
let usage_summary: (i64, f64) = sqlx::query_as(
|
||||
r#"
|
||||
SELECT
|
||||
COUNT(l.id) AS total,
|
||||
COALESCE(SUM(l.cost), 0.0) AS total_cost
|
||||
FROM usage_logs l
|
||||
JOIN downstream_api_keys k ON k.id = l.downstream_key_id
|
||||
WHERE k.user_id = ?
|
||||
"#,
|
||||
)
|
||||
.bind(user_id)
|
||||
.fetch_one(&*state.db)
|
||||
.await?;
|
||||
|
||||
let picture_summary: (i64, i64) = sqlx::query_as(
|
||||
r#"
|
||||
SELECT
|
||||
COUNT(*) AS total,
|
||||
COALESCE(SUM(CASE WHEN status = 'succeeded' THEN 1 ELSE 0 END), 0) AS success_count
|
||||
FROM background_tasks
|
||||
WHERE task_type = ?
|
||||
AND json_extract(result_json, '$.userId') = ?
|
||||
"#,
|
||||
)
|
||||
.bind("picture-agent-generation")
|
||||
.bind(user_id)
|
||||
.fetch_one(&*state.db)
|
||||
.await?;
|
||||
|
||||
Ok((
|
||||
proxy_summary.0 + usage_summary.0 + picture_summary.0,
|
||||
proxy_summary.1 + usage_summary.0 + picture_summary.1,
|
||||
proxy_summary.2 + usage_summary.1,
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct UpdateProfilePayload {
|
||||
username: Option<String>,
|
||||
@@ -537,13 +614,7 @@ async fn get_user_summary(
|
||||
headers: HeaderMap,
|
||||
) -> AppResult<Json<Value>> {
|
||||
let user_id = current_user_id(&headers, &state)?;
|
||||
let items = load_user_log_items(&state, user_id).await?;
|
||||
let total = items.len() as i64;
|
||||
let success_count = items
|
||||
.iter()
|
||||
.filter(|item| user_log_status(item).as_str() == "success")
|
||||
.count() as i64;
|
||||
let total_cost = items.iter().map(user_log_cost).sum::<f64>();
|
||||
let (total, success_count, total_cost) = load_user_log_summary(&state, user_id).await?;
|
||||
|
||||
Ok(Json(json!({
|
||||
"totalRequests": total,
|
||||
@@ -571,14 +642,8 @@ async fn get_user_dashboard(
|
||||
.fetch_all(&*state.db)
|
||||
.await?;
|
||||
|
||||
let items = load_user_log_items(&state, user_id).await?;
|
||||
let total = items.len() as i64;
|
||||
let success_count = items
|
||||
.iter()
|
||||
.filter(|item| user_log_status(item).as_str() == "success")
|
||||
.count() as i64;
|
||||
let total_cost = items.iter().map(user_log_cost).sum::<f64>();
|
||||
let recent_logs = items.into_iter().take(5).collect::<Vec<_>>();
|
||||
let (total, success_count, total_cost) = load_user_log_summary(&state, user_id).await?;
|
||||
let recent_logs = load_user_log_items_limited(&state, user_id, 5, true).await?;
|
||||
|
||||
Ok(Json(json!({
|
||||
"user": {
|
||||
|
||||
Reference in New Issue
Block a user