bctx-cloud-core 0.1.23

bctx-cloud-core — cloud client and server for Vault sync, dashboard API, billing
Documentation
use axum::{extract::State, http::StatusCode, response::IntoResponse, Json};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

use crate::server::{AppState, AuthUser};

#[derive(Serialize)]
pub struct AdminStatsResp {
    pub total_users: i64,
    pub users_by_tier: HashMap<String, i64>,
    pub mrr_usd: f64,
    pub total_tokens_saved_all_time: i64,
    pub tokens_saved_last_30d: i64,
    pub top_commands: Vec<TopCommand>,
    pub new_users_last_7d: i64,
    pub new_users_last_30d: i64,
}

#[derive(Serialize)]
pub struct TopCommand {
    pub program: String,
    pub tokens_saved: i64,
}

pub async fn stats(
    State(state): State<AppState>,
    AuthUser(user_id): AuthUser,
) -> impl IntoResponse {
    let user = match state.db.get_user(&user_id) {
        Some(u) => u,
        None => {
            return (
                StatusCode::UNAUTHORIZED,
                Json(serde_json::json!({"error": "not found"})),
            )
                .into_response();
        }
    };

    if !user.is_admin {
        return (
            StatusCode::FORBIDDEN,
            Json(serde_json::json!({"error": "forbidden"})),
        )
            .into_response();
    }

    let s = state.db.admin_stats();

    let resp = AdminStatsResp {
        total_users: s.total_users,
        users_by_tier: s.users_by_tier.into_iter().collect(),
        mrr_usd: s.mrr_usd,
        total_tokens_saved_all_time: s.total_tokens_saved_all_time,
        tokens_saved_last_30d: s.tokens_saved_last_30d,
        top_commands: s
            .top_commands
            .into_iter()
            .map(|(program, tokens_saved)| TopCommand {
                program,
                tokens_saved,
            })
            .collect(),
        new_users_last_7d: s.new_users_last_7d,
        new_users_last_30d: s.new_users_last_30d,
    };

    Json(resp).into_response()
}

#[derive(Deserialize)]
pub struct PromoteReq {
    pub email: String,
}

async fn set_admin(
    state: AppState,
    user_id: String,
    email: String,
    is_admin: bool,
) -> impl IntoResponse {
    let caller = match state.db.get_user(&user_id) {
        Some(u) => u,
        None => {
            return (
                StatusCode::UNAUTHORIZED,
                Json(serde_json::json!({"error": "not found"})),
            )
                .into_response();
        }
    };

    if !caller.is_admin {
        return (
            StatusCode::FORBIDDEN,
            Json(serde_json::json!({"error": "forbidden"})),
        )
            .into_response();
    }

    match state.db.set_admin_by_email(&email, is_admin) {
        Ok(true) => Json(serde_json::json!({"ok": true, "email": email})).into_response(),
        Ok(false) => (
            StatusCode::NOT_FOUND,
            Json(serde_json::json!({"error": "user not found"})),
        )
            .into_response(),
        Err(e) => (
            StatusCode::INTERNAL_SERVER_ERROR,
            Json(serde_json::json!({"error": e.to_string()})),
        )
            .into_response(),
    }
}

pub async fn promote(
    State(state): State<AppState>,
    AuthUser(user_id): AuthUser,
    Json(req): Json<PromoteReq>,
) -> impl IntoResponse {
    set_admin(state, user_id, req.email, true).await
}

pub async fn demote(
    State(state): State<AppState>,
    AuthUser(user_id): AuthUser,
    Json(req): Json<PromoteReq>,
) -> impl IntoResponse {
    set_admin(state, user_id, req.email, false).await
}