use super::data::ContractEvents;
use crate::http::chain::graphql::{DBContext, OptResult};
use async_graphql::{Context, FieldError};
use base64::{Engine, engine::general_purpose::STANDARD as B64};
use dusk_core::abi::CONTRACT_ID_BYTES;
const DEFAULT_LIMIT: i64 = 50;
const MAX_LIMIT: i64 = 200;
pub async fn events_by_height(
ctx: &Context<'_>,
height: i64,
) -> OptResult<ContractEvents> {
let (_, archive) = ctx.data::<DBContext>()?;
let events = if height < 0 {
archive.fetch_json_last_events().await
} else {
archive.fetch_json_events_by_height(height).await
}
.map_err(|e| FieldError::new(format!("Cannot fetch events: {e}")))?;
Ok(Some(ContractEvents(serde_json::from_str(&events)?)))
}
pub async fn events_by_hash(
ctx: &Context<'_>,
hash: String,
) -> OptResult<ContractEvents> {
let (_, archive) = ctx.data::<DBContext>()?;
let events = archive
.fetch_json_events_by_hash(&hash)
.await
.map_err(|e| FieldError::new(format!("Cannot fetch events: {}", e)))?;
Ok(Some(ContractEvents(serde_json::from_str(&events)?)))
}
pub async fn finalized_events_by_contract(
ctx: &Context<'_>,
hex_contract_id: String,
limit: Option<i64>,
cursor: Option<String>,
) -> OptResult<ContractEvents> {
let (_, archive) = ctx.data::<DBContext>()?;
if hex_contract_id.len() != CONTRACT_ID_BYTES * 2 {
return Err(FieldError::new("Invalid contract_id"));
}
let clamped_limit = limit.unwrap_or(DEFAULT_LIMIT).clamp(1, MAX_LIMIT);
let cursor_id = match cursor {
None => None,
Some(s) => decode_cursor_id(&s)
.ok_or_else(|| FieldError::new("Invalid cursor"))?
.into(),
};
let (events, next_id, has_next) = archive
.fetch_finalized_events_from_contract(
&hex_contract_id,
clamped_limit,
cursor_id,
)
.await
.map_err(|e| FieldError::new(format!("Cannot fetch events: {e}")))?;
let start_cursor = events.first().map(|e| encode_cursor_id(e.id));
let end_cursor = next_id.map(encode_cursor_id);
let value = serde_json::json!({
"events": events,
"endCursor": end_cursor,
"startCursor": start_cursor,
"hasNextPage": has_next
});
Ok(Some(ContractEvents(value)))
}
fn encode_cursor_id(id: i64) -> String {
B64.encode(format!("v1:{}", id))
}
fn decode_cursor_id(s: &str) -> Option<i64> {
let bytes = B64.decode(s).ok()?;
let text = String::from_utf8(bytes).ok()?;
let (v, rest) = text.split_once(':')?;
if v != "v1" {
return None;
}
rest.parse::<i64>().ok()
}