diff --git a/orm/src/transactions.rs b/orm/src/transactions.rs index 0c5a8fc7..3e274069 100644 --- a/orm/src/transactions.rs +++ b/orm/src/transactions.rs @@ -9,7 +9,16 @@ use crate::schema::{ inner_transactions, transaction_history, wrapper_transactions, }; -#[derive(Debug, Clone, Serialize, Deserialize, diesel_derive_enum::DbEnum)] +#[derive( + Debug, + Clone, + Serialize, + Deserialize, + diesel_derive_enum::DbEnum, + Eq, + PartialEq, + Hash, +)] #[ExistingTypePath = "crate::schema::sql_types::TransactionKind"] pub enum TransactionKindDb { TransparentTransfer, diff --git a/swagger.yml b/swagger.yml index 82b1138a..fc8b8478 100644 --- a/swagger.yml +++ b/swagger.yml @@ -1004,6 +1004,11 @@ paths: minItems: 1 maxItems: 10 description: The list of address. Must contain at least 1 element + - in: query + name: transaction_types + schema: + type: string + description: Comma-separated list of transaction types to filter by. If not provided, all transaction types are returned. The options are transparentTransfer, shieldedTransfer, shieldingTransfer, unshieldingTransfer, mixedTransfer, ibcMsgTransfer, ibcTransparentTransfer, ibcShieldingTransfer, ibcUnshieldingTransfer, bond, redelegation, unbond, withdraw, claimRewards, voteProposal, initProposal, changeMetadata, changeCommission, revealPk, becomeValidator, reactivateValidator, deactivateValidator, unjailValidator responses: "200": description: Pagined historic transaction list. diff --git a/webserver/src/dto/transaction.rs b/webserver/src/dto/transaction.rs index 5f14eda2..1fac1113 100644 --- a/webserver/src/dto/transaction.rs +++ b/webserver/src/dto/transaction.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + use serde::{Deserialize, Serialize}; use validator::Validate; @@ -8,4 +10,6 @@ pub struct TransactionHistoryQueryParams { pub page: Option, #[validate(length(min = 1, max = 10))] pub addresses: Vec, + #[validate(length(min = 1, max = 20))] + pub transaction_types: Option>, } diff --git a/webserver/src/handler/transaction.rs b/webserver/src/handler/transaction.rs index 9676afcb..628c9deb 100644 --- a/webserver/src/handler/transaction.rs +++ b/webserver/src/handler/transaction.rs @@ -74,7 +74,7 @@ pub async fn get_transaction_history( let (transactions, total_pages, total_items) = state .transaction_service - .get_addresses_history(query.addresses, page) + .get_addresses_history(query.addresses, page, query.transaction_types) .await?; let response = diff --git a/webserver/src/repository/tranasaction.rs b/webserver/src/repository/tranasaction.rs index a82a5cc3..b80f9b66 100644 --- a/webserver/src/repository/tranasaction.rs +++ b/webserver/src/repository/tranasaction.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + use axum::async_trait; use diesel::{ ExpressionMethods, JoinOnDsl, QueryDsl, RunQueryDsl, SelectableHelper, @@ -6,7 +8,8 @@ use orm::schema::{ inner_transactions, transaction_history, wrapper_transactions, }; use orm::transactions::{ - InnerTransactionDb, TransactionHistoryDb, WrapperTransactionDb, + InnerTransactionDb, TransactionHistoryDb, TransactionKindDb, + WrapperTransactionDb, }; use super::utils::{Paginate, PaginatedResponseDb}; @@ -37,6 +40,7 @@ pub trait TransactionRepositoryTrait { &self, addresses: Vec, page: i64, + transaction_types: Option>, ) -> Result< PaginatedResponseDb<(TransactionHistoryDb, InnerTransactionDb, i32)>, String, @@ -108,6 +112,7 @@ impl TransactionRepositoryTrait for TransactionRepository { &self, addresses: Vec, page: i64, + transaction_types: Option>, ) -> Result< PaginatedResponseDb<(TransactionHistoryDb, InnerTransactionDb, i32)>, String, @@ -115,10 +120,19 @@ impl TransactionRepositoryTrait for TransactionRepository { let conn = self.app_state.get_db_connection().await; conn.interact(move |conn| { - transaction_history::table + let mut query = transaction_history::table .filter(transaction_history::dsl::target.eq_any(addresses)) .inner_join(inner_transactions::table.on(transaction_history::dsl::inner_tx_id.eq(inner_transactions::dsl::id))) .inner_join(wrapper_transactions::table.on(inner_transactions::dsl::wrapper_id.eq(wrapper_transactions::dsl::id))) + .into_boxed(); + // Apply transaction type filter if provided + if let Some(types) = transaction_types { + if !types.is_empty() { + query = query.filter(inner_transactions::dsl::kind.eq_any(types)); + } + } + + query .order(wrapper_transactions::dsl::block_height.desc()) .select((transaction_history::all_columns, inner_transactions::all_columns, wrapper_transactions::dsl::block_height)) .paginate(page) diff --git a/webserver/src/service/transaction.rs b/webserver/src/service/transaction.rs index 1a20a6dd..19eb55d0 100644 --- a/webserver/src/service/transaction.rs +++ b/webserver/src/service/transaction.rs @@ -1,4 +1,6 @@ -use orm::transactions::WrapperTransactionDb; +use std::collections::HashSet; + +use orm::transactions::{TransactionKindDb, WrapperTransactionDb}; use crate::appstate::AppState; use crate::error::transaction::TransactionError; @@ -75,14 +77,80 @@ impl TransactionService { Ok(inner_txs.into_iter().map(InnerTransaction::from).collect()) } + // Helper function to parse transaction types from comma-separated string + fn parse_transaction_types( + transaction_types: HashSet, + ) -> HashSet { + transaction_types + .iter() + .filter_map(|type_str| { + let type_str = type_str.trim(); + match type_str { + "transparentTransfer" => { + Some(TransactionKindDb::TransparentTransfer) + } + "shieldedTransfer" => { + Some(TransactionKindDb::ShieldedTransfer) + } + "shieldingTransfer" => { + Some(TransactionKindDb::ShieldingTransfer) + } + "unshieldingTransfer" => { + Some(TransactionKindDb::UnshieldingTransfer) + } + "mixedTransfer" => Some(TransactionKindDb::MixedTransfer), + "ibcMsgTransfer" => Some(TransactionKindDb::IbcMsgTransfer), + "ibcTransparentTransfer" => { + Some(TransactionKindDb::IbcTransparentTransfer) + } + "ibcShieldingTransfer" => { + Some(TransactionKindDb::IbcShieldingTransfer) + } + "ibcUnshieldingTransfer" => { + Some(TransactionKindDb::IbcUnshieldingTransfer) + } + "bond" => Some(TransactionKindDb::Bond), + "redelegation" => Some(TransactionKindDb::Redelegation), + "unbond" => Some(TransactionKindDb::Unbond), + "withdraw" => Some(TransactionKindDb::Withdraw), + "claimRewards" => Some(TransactionKindDb::ClaimRewards), + "voteProposal" => Some(TransactionKindDb::VoteProposal), + "initProposal" => Some(TransactionKindDb::InitProposal), + "changeMetadata" => Some(TransactionKindDb::ChangeMetadata), + "changeCommission" => { + Some(TransactionKindDb::ChangeCommission) + } + "revealPk" => Some(TransactionKindDb::RevealPk), + "becomeValidator" => { + Some(TransactionKindDb::BecomeValidator) + } + "reactivateValidator" => { + Some(TransactionKindDb::ReactivateValidator) + } + "deactivateValidator" => { + Some(TransactionKindDb::DeactivateValidator) + } + "unjailValidator" => { + Some(TransactionKindDb::UnjailValidator) + } + _ => None, // Skip invalid types + } + }) + .collect::>() + } + pub async fn get_addresses_history( &self, addresses: Vec, page: u64, + transaction_types: Option>, ) -> Result<(Vec, u64, u64), TransactionError> { + let transaction_types = + transaction_types.map(Self::parse_transaction_types); + let (txs, total_pages, total_items) = self .transaction_repo - .find_addresses_history(addresses, page as i64) + .find_addresses_history(addresses, page as i64, transaction_types) .await .map_err(TransactionError::Database)?;