Skip to main content

tuitbot_server/routes/
costs.rs

1//! Cost tracking endpoints — LLM and X API usage summaries and breakdowns.
2
3use std::sync::Arc;
4
5use axum::extract::{Query, State};
6use axum::Json;
7use serde::Deserialize;
8use tuitbot_core::storage::{llm_usage, x_api_usage};
9
10use crate::account::AccountContext;
11use crate::error::ApiError;
12use crate::state::AppState;
13
14/// Query parameters for endpoints that accept a `days` window.
15#[derive(Deserialize)]
16pub struct DaysQuery {
17    #[serde(default = "default_days")]
18    pub days: u32,
19}
20
21fn default_days() -> u32 {
22    30
23}
24
25/// `GET /api/costs/summary` — cost totals across time windows.
26pub async fn summary(
27    State(state): State<Arc<AppState>>,
28    ctx: AccountContext,
29) -> Result<Json<llm_usage::CostSummary>, ApiError> {
30    let summary = llm_usage::get_cost_summary_for(&state.db, &ctx.account_id).await?;
31    Ok(Json(summary))
32}
33
34/// `GET /api/costs/daily?days=30` — per-day cost data for charts.
35pub async fn daily(
36    State(state): State<Arc<AppState>>,
37    ctx: AccountContext,
38    Query(params): Query<DaysQuery>,
39) -> Result<Json<Vec<llm_usage::DailyCostSummary>>, ApiError> {
40    let data = llm_usage::get_daily_costs_for(&state.db, &ctx.account_id, params.days).await?;
41    Ok(Json(data))
42}
43
44/// `GET /api/costs/by-model?days=30` — cost breakdown by provider + model.
45pub async fn by_model(
46    State(state): State<Arc<AppState>>,
47    ctx: AccountContext,
48    Query(params): Query<DaysQuery>,
49) -> Result<Json<Vec<llm_usage::ModelCostBreakdown>>, ApiError> {
50    let data = llm_usage::get_model_breakdown_for(&state.db, &ctx.account_id, params.days).await?;
51    Ok(Json(data))
52}
53
54/// `GET /api/costs/by-type?days=30` — cost breakdown by generation type.
55pub async fn by_type(
56    State(state): State<Arc<AppState>>,
57    ctx: AccountContext,
58    Query(params): Query<DaysQuery>,
59) -> Result<Json<Vec<llm_usage::TypeCostBreakdown>>, ApiError> {
60    let data = llm_usage::get_type_breakdown_for(&state.db, &ctx.account_id, params.days).await?;
61    Ok(Json(data))
62}
63
64// --- X API usage endpoints ---
65
66/// `GET /api/costs/x-api/summary` — X API call totals across time windows.
67pub async fn x_api_summary(
68    State(state): State<Arc<AppState>>,
69    ctx: AccountContext,
70) -> Result<Json<x_api_usage::XApiUsageSummary>, ApiError> {
71    let summary = x_api_usage::get_usage_summary_for(&state.db, &ctx.account_id).await?;
72    Ok(Json(summary))
73}
74
75/// `GET /api/costs/x-api/daily?days=30` — per-day X API call data for charts.
76pub async fn x_api_daily(
77    State(state): State<Arc<AppState>>,
78    ctx: AccountContext,
79    Query(params): Query<DaysQuery>,
80) -> Result<Json<Vec<x_api_usage::DailyXApiUsage>>, ApiError> {
81    let data = x_api_usage::get_daily_usage_for(&state.db, &ctx.account_id, params.days).await?;
82    Ok(Json(data))
83}
84
85/// `GET /api/costs/x-api/by-endpoint?days=30` — X API usage breakdown by endpoint.
86pub async fn x_api_by_endpoint(
87    State(state): State<Arc<AppState>>,
88    ctx: AccountContext,
89    Query(params): Query<DaysQuery>,
90) -> Result<Json<Vec<x_api_usage::EndpointBreakdown>>, ApiError> {
91    let data =
92        x_api_usage::get_endpoint_breakdown_for(&state.db, &ctx.account_id, params.days).await?;
93    Ok(Json(data))
94}