bctx-cloud-core 0.1.6

bctx-cloud-core — cloud client and server for Vault sync, dashboard API, billing
Documentation
use crate::server::{AppError, AppState, AuthUser};
use anyhow::anyhow;
use axum::{
    extract::{Path, State},
    Json,
};
use serde::{Deserialize, Serialize};
use vault::fact::MemoFact;

#[derive(Deserialize)]
pub struct PushReq {
    pub project_hash: String,
    pub facts: Vec<MemoFact>,
    pub client_version: String,
}

#[derive(Serialize)]
pub struct PushResp {
    pub accepted: usize,
    pub merged: usize,
    pub server_version: u64,
}

pub async fn push_vault(
    State(state): State<AppState>,
    AuthUser(user_id): AuthUser,
    Json(req): Json<PushReq>,
) -> Result<Json<PushResp>, AppError> {
    let accepted = req.facts.len();
    let facts_json = serde_json::to_string(&req.facts).map_err(|e| AppError(anyhow!(e)))?;
    let version = state
        .db
        .upsert_vault(&user_id, &req.project_hash, &facts_json)
        .map_err(|e| AppError(anyhow!(e)))?;
    Ok(Json(PushResp {
        accepted,
        merged: 0,
        server_version: version as u64,
    }))
}

#[derive(Serialize)]
pub struct PullResp {
    pub facts: Vec<MemoFact>,
    pub server_version: u64,
}

pub async fn pull_vault(
    State(state): State<AppState>,
    AuthUser(user_id): AuthUser,
    Path(project_hash): Path<String>,
) -> Result<Json<PullResp>, AppError> {
    let record = state.db.get_vault(&user_id, &project_hash);
    match record {
        Some(r) => {
            let facts: Vec<MemoFact> = serde_json::from_str(&r.facts_json).unwrap_or_default();
            Ok(Json(PullResp {
                facts,
                server_version: r.server_version as u64,
            }))
        }
        None => Ok(Json(PullResp {
            facts: vec![],
            server_version: 0,
        })),
    }
}

#[derive(Deserialize)]
pub struct BeaconPushReq {
    pub tokens_sent: i64,
    pub tokens_saved: i64,
    pub skill: String,
}

pub async fn push_signals(
    State(state): State<AppState>,
    AuthUser(user_id): AuthUser,
    Json(req): Json<BeaconPushReq>,
) -> Result<Json<serde_json::Value>, AppError> {
    if !state.db.check_signal_rate_limit(&user_id) {
        return Ok(Json(serde_json::json!({
            "ok": false,
            "reason": "rate_limited",
            "limits": { "per_minute": 120, "per_day": 10000 }
        })));
    }
    state
        .db
        .record_usage(&user_id, req.tokens_sent, req.tokens_saved, &req.skill)
        .map_err(|e| AppError(anyhow!(e)))?;
    Ok(Json(serde_json::json!({"ok": true})))
}