server: snapshot

This commit is contained in:
nym21
2025-12-15 17:33:49 +01:00
parent 825a4a77c0
commit 692a1889ab
8 changed files with 242 additions and 183 deletions
+41 -41
View File
@@ -27,11 +27,11 @@ pub fn create_openapi() -> OpenApi {
let tags = vec![
Tag {
name: "Addresses".to_string(),
name: "Metrics".to_string(),
description: Some(
"Query Bitcoin address data including balances, transaction history, and UTXOs. \
Supports all address types: P2PKH, P2SH, P2WPKH, P2WSH, and P2TR."
.to_string()
"Access Bitcoin network metrics and time-series data. Query historical statistics \
across various indexes with JSON or CSV output."
.to_string(),
),
..Default::default()
},
@@ -40,42 +40,7 @@ pub fn create_openapi() -> OpenApi {
description: Some(
"Retrieve block data by hash or height. Access block headers, transaction lists, \
and raw block bytes."
.to_string()
),
..Default::default()
},
Tag {
name: "Mempool".to_string(),
description: Some(
"Monitor unconfirmed transactions and fee estimates. Get mempool statistics, \
transaction IDs, and recommended fee rates for different confirmation targets."
.to_string()
),
..Default::default()
},
Tag {
name: "Metrics".to_string(),
description: Some(
"Access Bitcoin network metrics and time-series data. Query historical statistics \
across various indexes (date, week, month, year, halving epoch) with JSON or CSV output."
.to_string()
),
..Default::default()
},
Tag {
name: "Mining".to_string(),
description: Some(
"Mining statistics including pool distribution, hashrate, difficulty adjustments, \
block rewards, and fee rates across configurable time periods."
.to_string()
),
..Default::default()
},
Tag {
name: "Server".to_string(),
description: Some(
"API metadata and health monitoring. Version information and service status."
.to_string()
.to_string(),
),
..Default::default()
},
@@ -84,7 +49,42 @@ pub fn create_openapi() -> OpenApi {
description: Some(
"Retrieve transaction data by txid. Access full transaction details, confirmation \
status, raw hex, and output spend information."
.to_string()
.to_string(),
),
..Default::default()
},
Tag {
name: "Addresses".to_string(),
description: Some(
"Query Bitcoin address data including balances, transaction history, and UTXOs. \
Supports all address types: P2PKH, P2SH, P2WPKH, P2WSH, and P2TR."
.to_string(),
),
..Default::default()
},
Tag {
name: "Mempool".to_string(),
description: Some(
"Monitor unconfirmed transactions and fee estimates. Get mempool statistics, \
transaction IDs, and recommended fee rates for different confirmation targets."
.to_string(),
),
..Default::default()
},
Tag {
name: "Mining".to_string(),
description: Some(
"Mining statistics including pool distribution, hashrate, difficulty adjustments, \
block rewards, and fee rates across configurable time periods."
.to_string(),
),
..Default::default()
},
Tag {
name: "Server".to_string(),
description: Some(
"API metadata and health monitoring. Version information and service status."
.to_string(),
),
..Default::default()
},
+5 -86
View File
@@ -1,15 +1,11 @@
#![doc = include_str!("../README.md")]
#![doc = "\n## Example\n\n```rust"]
#![doc = include_str!("../examples/server.rs")]
#![doc = "```"]
use std::{ops::Deref, path::PathBuf, sync::Arc, time::Duration};
use std::{path::PathBuf, sync::Arc, time::Duration};
use aide::axum::ApiRouter;
use api::ApiRoutes;
use axum::{
Extension,
body::{Body, Bytes},
body::Body,
http::{Request, Response, StatusCode, Uri},
middleware::Next,
response::Redirect,
@@ -20,7 +16,6 @@ use brk_error::Result;
use brk_logger::OwoColorize;
use brk_mcp::route::MCPRoutes;
use brk_query::AsyncQuery;
use files::FilesRoutes;
use log::{error, info};
use quick_cache::sync::Cache;
use tokio::net::TcpListener;
@@ -31,89 +26,13 @@ mod api;
pub mod cache;
mod extended;
mod files;
mod state;
use api::*;
pub use cache::{CacheParams, CacheStrategy};
use extended::*;
#[derive(Clone)]
pub struct AppState {
query: AsyncQuery,
path: Option<PathBuf>,
cache: Arc<Cache<String, Bytes>>,
}
impl Deref for AppState {
type Target = AsyncQuery;
fn deref(&self) -> &Self::Target {
&self.query
}
}
impl AppState {
/// JSON response with caching
pub async fn cached_json<T, F>(
&self,
headers: &axum::http::HeaderMap,
strategy: CacheStrategy,
f: F,
) -> axum::http::Response<axum::body::Body>
where
T: serde::Serialize + Send + 'static,
F: FnOnce(&brk_query::Query) -> brk_error::Result<T> + Send + 'static,
{
let params = CacheParams::resolve(&strategy, || self.sync(|q| q.height().into()));
if params.matches_etag(headers) {
return ResponseExtended::new_not_modified();
}
match self.run(f).await {
Ok(value) => ResponseExtended::new_json_cached(&value, &params),
Err(e) => ResultExtended::<T>::to_json_response(Err(e), params.etag_str()),
}
}
/// Text response with caching
pub async fn cached_text<T, F>(
&self,
headers: &axum::http::HeaderMap,
strategy: CacheStrategy,
f: F,
) -> axum::http::Response<axum::body::Body>
where
T: AsRef<str> + Send + 'static,
F: FnOnce(&brk_query::Query) -> brk_error::Result<T> + Send + 'static,
{
let params = CacheParams::resolve(&strategy, || self.sync(|q| q.height().into()));
if params.matches_etag(headers) {
return ResponseExtended::new_not_modified();
}
match self.run(f).await {
Ok(value) => ResponseExtended::new_text_cached(value.as_ref(), &params),
Err(e) => ResultExtended::<T>::to_text_response(Err(e), params.etag_str()),
}
}
/// Binary response with caching
pub async fn cached_bytes<T, F>(
&self,
headers: &axum::http::HeaderMap,
strategy: CacheStrategy,
f: F,
) -> axum::http::Response<axum::body::Body>
where
T: Into<Vec<u8>> + Send + 'static,
F: FnOnce(&brk_query::Query) -> brk_error::Result<T> + Send + 'static,
{
let params = CacheParams::resolve(&strategy, || self.sync(|q| q.height().into()));
if params.matches_etag(headers) {
return ResponseExtended::new_not_modified();
}
match self.run(f).await {
Ok(value) => ResponseExtended::new_bytes_cached(value.into(), &params),
Err(e) => ResultExtended::<T>::to_bytes_response(Err(e), params.etag_str()),
}
}
}
use files::FilesRoutes;
use state::*;
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
+93
View File
@@ -0,0 +1,93 @@
use std::{ops::Deref, path::PathBuf, sync::Arc};
use axum::{
body::{Body, Bytes},
http::{HeaderMap, Response},
};
use brk_query::AsyncQuery;
use quick_cache::sync::Cache;
use serde::Serialize;
use crate::{
CacheParams, CacheStrategy,
extended::{ResponseExtended, ResultExtended},
};
#[derive(Clone)]
pub struct AppState {
pub query: AsyncQuery,
pub path: Option<PathBuf>,
pub cache: Arc<Cache<String, Bytes>>,
}
impl Deref for AppState {
type Target = AsyncQuery;
fn deref(&self) -> &Self::Target {
&self.query
}
}
impl AppState {
/// JSON response with caching
pub async fn cached_json<T, F>(
&self,
headers: &HeaderMap,
strategy: CacheStrategy,
f: F,
) -> Response<Body>
where
T: Serialize + Send + 'static,
F: FnOnce(&brk_query::Query) -> brk_error::Result<T> + Send + 'static,
{
let params = CacheParams::resolve(&strategy, || self.sync(|q| q.height().into()));
if params.matches_etag(headers) {
return ResponseExtended::new_not_modified();
}
match self.run(f).await {
Ok(value) => ResponseExtended::new_json_cached(&value, &params),
Err(e) => ResultExtended::<T>::to_json_response(Err(e), params.etag_str()),
}
}
/// Text response with caching
pub async fn cached_text<T, F>(
&self,
headers: &HeaderMap,
strategy: CacheStrategy,
f: F,
) -> Response<Body>
where
T: AsRef<str> + Send + 'static,
F: FnOnce(&brk_query::Query) -> brk_error::Result<T> + Send + 'static,
{
let params = CacheParams::resolve(&strategy, || self.sync(|q| q.height().into()));
if params.matches_etag(headers) {
return ResponseExtended::new_not_modified();
}
match self.run(f).await {
Ok(value) => ResponseExtended::new_text_cached(value.as_ref(), &params),
Err(e) => ResultExtended::<T>::to_text_response(Err(e), params.etag_str()),
}
}
/// Binary response with caching
pub async fn cached_bytes<T, F>(
&self,
headers: &HeaderMap,
strategy: CacheStrategy,
f: F,
) -> Response<Body>
where
T: Into<Vec<u8>> + Send + 'static,
F: FnOnce(&brk_query::Query) -> brk_error::Result<T> + Send + 'static,
{
let params = CacheParams::resolve(&strategy, || self.sync(|q| q.height().into()));
if params.matches_etag(headers) {
return ResponseExtended::new_not_modified();
}
match self.run(f).await {
Ok(value) => ResponseExtended::new_bytes_cached(value.into(), &params),
Err(e) => ResultExtended::<T>::to_bytes_response(Err(e), params.etag_str()),
}
}
}