-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
19 changed files
with
798 additions
and
114 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
use axum::{ | ||
extract::{Query, State}, | ||
Extension, | ||
}; | ||
use std::sync::Arc; | ||
use validator::Validate; | ||
|
||
use axum_web::{ | ||
context::ReqContext, | ||
erring::{HTTPError, SuccessResponse}, | ||
object::PackObject, | ||
}; | ||
use ns_protocol::index::{Inscription, InvalidInscription}; | ||
|
||
use crate::api::{IndexerAPI, QueryHeight, QueryName, QueryNamePagination}; | ||
use crate::db; | ||
|
||
pub struct InscriptionAPI; | ||
|
||
impl InscriptionAPI { | ||
pub async fn get_last_accepted( | ||
Extension(ctx): Extension<Arc<ReqContext>>, | ||
to: PackObject<()>, | ||
State(api): State<Arc<IndexerAPI>>, | ||
) -> Result<PackObject<SuccessResponse<Option<Inscription>>>, HTTPError> { | ||
ctx.set("action", "get_last_accepted_inscription".into()) | ||
.await; | ||
|
||
let last_accepted_state = api.state.last_accepted.read().await; | ||
|
||
Ok(to.with(SuccessResponse::new(last_accepted_state.clone()))) | ||
} | ||
|
||
pub async fn get_best( | ||
Extension(ctx): Extension<Arc<ReqContext>>, | ||
to: PackObject<()>, | ||
State(api): State<Arc<IndexerAPI>>, | ||
) -> Result<PackObject<SuccessResponse<Option<Inscription>>>, HTTPError> { | ||
ctx.set("action", "get_best_inscription".into()).await; | ||
|
||
let best_inscriptions_state = api.state.best_inscriptions.read().await; | ||
let mut inscription = best_inscriptions_state.last().cloned(); | ||
if inscription.is_none() { | ||
let last_accepted_state = api.state.last_accepted.read().await; | ||
inscription = last_accepted_state.clone(); | ||
} | ||
|
||
Ok(to.with(SuccessResponse::new(inscription))) | ||
} | ||
|
||
pub async fn get( | ||
State(app): State<Arc<IndexerAPI>>, | ||
Extension(ctx): Extension<Arc<ReqContext>>, | ||
to: PackObject<()>, | ||
input: Query<QueryName>, | ||
) -> Result<PackObject<SuccessResponse<Inscription>>, HTTPError> { | ||
input.validate()?; | ||
if input.sequence.is_none() { | ||
return Err(HTTPError::new(400, "sequence is required".to_string())); | ||
} | ||
|
||
let name = input.name.clone(); | ||
let sequence = input.sequence.unwrap(); | ||
ctx.set_kvs(vec![ | ||
("action", "get_inscription".into()), | ||
("name", name.clone().into()), | ||
("sequence", sequence.into()), | ||
]) | ||
.await; | ||
|
||
let mut inscription = db::Inscription::with_pk(name, sequence); | ||
inscription.get_one(&app.scylla, vec![]).await?; | ||
|
||
Ok(to.with(SuccessResponse::new(inscription.to_index()?))) | ||
} | ||
|
||
pub async fn get_by_height( | ||
State(app): State<Arc<IndexerAPI>>, | ||
Extension(ctx): Extension<Arc<ReqContext>>, | ||
to: PackObject<()>, | ||
input: Query<QueryHeight>, | ||
) -> Result<PackObject<SuccessResponse<Inscription>>, HTTPError> { | ||
input.validate()?; | ||
|
||
let height = input.height; | ||
ctx.set_kvs(vec![ | ||
("action", "get_inscription_by_height".into()), | ||
("height", height.into()), | ||
]) | ||
.await; | ||
|
||
let inscription = db::Inscription::get_by_height(&app.scylla, height, vec![]).await?; | ||
|
||
Ok(to.with(SuccessResponse::new(inscription.to_index()?))) | ||
} | ||
|
||
pub async fn list_best( | ||
Extension(ctx): Extension<Arc<ReqContext>>, | ||
to: PackObject<()>, | ||
State(api): State<Arc<IndexerAPI>>, | ||
) -> Result<PackObject<SuccessResponse<Vec<Inscription>>>, HTTPError> { | ||
ctx.set("action", "list_best_inscriptions".into()).await; | ||
let best_inscriptions_state = api.state.best_inscriptions.read().await; | ||
Ok(to.with(SuccessResponse::new(best_inscriptions_state.clone()))) | ||
} | ||
|
||
pub async fn list_by_block_height( | ||
State(app): State<Arc<IndexerAPI>>, | ||
Extension(ctx): Extension<Arc<ReqContext>>, | ||
to: PackObject<()>, | ||
input: Query<QueryHeight>, | ||
) -> Result<PackObject<SuccessResponse<Vec<Inscription>>>, HTTPError> { | ||
input.validate()?; | ||
|
||
let height = input.height; | ||
ctx.set_kvs(vec![ | ||
("action", "list_inscriptions_block_height".into()), | ||
("height", height.into()), | ||
]) | ||
.await; | ||
|
||
let res = db::Inscription::list_by_block_height(&app.scylla, height, vec![]).await?; | ||
let mut inscriptions: Vec<Inscription> = Vec::with_capacity(res.len()); | ||
for i in res { | ||
inscriptions.push(i.to_index()?); | ||
} | ||
Ok(to.with(SuccessResponse::new(inscriptions))) | ||
} | ||
|
||
pub async fn list_by_name( | ||
State(app): State<Arc<IndexerAPI>>, | ||
Extension(ctx): Extension<Arc<ReqContext>>, | ||
to: PackObject<()>, | ||
input: Query<QueryNamePagination>, | ||
) -> Result<PackObject<SuccessResponse<Vec<Inscription>>>, HTTPError> { | ||
input.validate()?; | ||
|
||
let name = input.name.clone(); | ||
ctx.set_kvs(vec![ | ||
("action", "list_inscriptions_by_name".into()), | ||
("name", name.clone().into()), | ||
]) | ||
.await; | ||
|
||
let res = db::Inscription::list_by_name( | ||
&app.scylla, | ||
&name, | ||
vec![], | ||
input.page_size.unwrap_or(10), | ||
input.page_token, | ||
) | ||
.await?; | ||
let mut inscriptions: Vec<Inscription> = Vec::with_capacity(res.len()); | ||
for i in res { | ||
inscriptions.push(i.to_index()?); | ||
} | ||
let next_sequence = if let Some(last) = inscriptions.last() { | ||
last.sequence | ||
} else { | ||
0 | ||
}; | ||
Ok(to.with(SuccessResponse { | ||
total_size: None, | ||
next_page_token: if next_sequence > 0 { | ||
Some(next_sequence.to_string()) | ||
} else { | ||
None | ||
}, | ||
result: inscriptions, | ||
})) | ||
} | ||
|
||
pub async fn list_invalid_by_name( | ||
State(app): State<Arc<IndexerAPI>>, | ||
Extension(ctx): Extension<Arc<ReqContext>>, | ||
to: PackObject<()>, | ||
input: Query<QueryName>, | ||
) -> Result<PackObject<SuccessResponse<Vec<InvalidInscription>>>, HTTPError> { | ||
input.validate()?; | ||
|
||
let name = input.name.clone(); | ||
ctx.set_kvs(vec![ | ||
("action", "list_invalid_inscriptions_by_name".into()), | ||
("name", name.clone().into()), | ||
]) | ||
.await; | ||
|
||
let res = db::InvalidInscription::list_by_name(&app.scylla, &name).await?; | ||
let mut inscriptions: Vec<InvalidInscription> = Vec::with_capacity(res.len()); | ||
for i in res { | ||
inscriptions.push(i.to_index()?); | ||
} | ||
|
||
Ok(to.with(SuccessResponse::new(inscriptions))) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
use axum::extract::State; | ||
use serde::{Deserialize, Serialize}; | ||
use std::sync::Arc; | ||
use validator::{Validate, ValidationError}; | ||
|
||
use axum_web::erring::{HTTPError, SuccessResponse}; | ||
use axum_web::object::PackObject; | ||
use ns_protocol::ns; | ||
|
||
use crate::db::scylladb::ScyllaDB; | ||
use crate::indexer::{Indexer, IndexerState}; | ||
|
||
mod inscription; | ||
mod name; | ||
mod service; | ||
|
||
pub use inscription::InscriptionAPI; | ||
pub use name::NameAPI; | ||
pub use service::ServiceAPI; | ||
|
||
#[derive(Serialize, Deserialize)] | ||
pub struct AppVersion { | ||
pub name: String, | ||
pub version: String, | ||
} | ||
|
||
#[derive(Serialize, Deserialize)] | ||
pub struct AppHealth { | ||
pub block_height: u64, | ||
pub inscription_height: u64, | ||
} | ||
|
||
pub struct IndexerAPI { | ||
pub(crate) scylla: Arc<ScyllaDB>, | ||
pub(crate) state: Arc<IndexerState>, | ||
} | ||
|
||
impl IndexerAPI { | ||
pub fn new(indexer: Arc<Indexer>) -> Self { | ||
Self { | ||
scylla: indexer.scylla.clone(), | ||
state: indexer.state.clone(), | ||
} | ||
} | ||
} | ||
|
||
pub async fn version( | ||
to: PackObject<()>, | ||
State(_): State<Arc<IndexerAPI>>, | ||
) -> PackObject<AppVersion> { | ||
to.with(AppVersion { | ||
name: crate::APP_NAME.to_string(), | ||
version: crate::APP_VERSION.to_string(), | ||
}) | ||
} | ||
|
||
pub async fn healthz( | ||
to: PackObject<()>, | ||
State(api): State<Arc<IndexerAPI>>, | ||
) -> Result<PackObject<SuccessResponse<AppHealth>>, HTTPError> { | ||
let last_accepted_state = api.state.last_accepted.read().await; | ||
let (block_height, height) = match *last_accepted_state { | ||
Some(ref last_accepted) => (last_accepted.block_height, last_accepted.height), | ||
None => (0, 0), | ||
}; | ||
Ok(to.with(SuccessResponse::new(AppHealth { | ||
block_height, | ||
inscription_height: height, | ||
}))) | ||
} | ||
|
||
#[derive(Debug, Deserialize, Validate)] | ||
pub struct QueryName { | ||
#[validate(custom = "validate_name")] | ||
pub name: String, | ||
#[validate(range(min = 0))] | ||
pub sequence: Option<i64>, | ||
#[validate(range(min = 0))] | ||
pub code: Option<i64>, | ||
} | ||
|
||
#[derive(Debug, Deserialize, Validate)] | ||
pub struct QueryHeight { | ||
#[validate(range(min = 0))] | ||
pub height: i64, | ||
} | ||
|
||
#[derive(Debug, Deserialize, Validate)] | ||
pub struct QueryNamePagination { | ||
#[validate(custom = "validate_name")] | ||
pub name: String, | ||
pub page_token: Option<i64>, | ||
#[validate(range(min = 2, max = 1000))] | ||
pub page_size: Option<u16>, | ||
} | ||
|
||
#[derive(Debug, Deserialize, Validate)] | ||
pub struct QueryPubkey { | ||
pub pubkey: String, | ||
} | ||
|
||
fn validate_name(name: &str) -> Result<(), ValidationError> { | ||
if !ns::valid_name(name) { | ||
return Err(ValidationError::new("invalid name")); | ||
} | ||
|
||
Ok(()) | ||
} |
Oops, something went wrong.