Skip to content

Commit

Permalink
feat(multi): enhance multichain search api (#1221)
Browse files Browse the repository at this point in the history
* feat: add /transactions endpoint

* feat: add nft api

* feat: update quick search api

* feat: add chains endpoint

* feat: add marketplace proxy methods

* fix: test

* feat: balance quick search entities

* feat: update dapps api

* fix: typo
  • Loading branch information
lok52 authored Feb 3, 2025
1 parent 44911b5 commit 19c7404
Show file tree
Hide file tree
Showing 23 changed files with 988 additions and 313 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ pub struct Model {
pub icon_url: Option<String>,
pub created_at: DateTime,
pub updated_at: DateTime,
#[sea_orm(column_type = "Text", nullable)]
pub name: Option<String>,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,43 +10,85 @@ pub fn new_client(url: Url) -> Result<Client, Error> {
Client::new(url, config)
}

pub struct SearchDapps {
pub params: SearchDappsParams,
}
pub mod search_dapps {
use super::*;

#[derive(Serialize, Clone, Debug, Default, PartialEq)]
pub struct SearchDappsParams {
pub query: String,
}
pub struct SearchDapps {
pub params: SearchDappsParams,
}

#[derive(Serialize, Clone, Debug, Default, PartialEq)]
pub struct SearchDappsParams {
pub title: Option<String>,
pub categories: Option<String>,
pub chain_ids: Option<String>,
}

impl Endpoint for SearchDapps {
type Response = Vec<DappWithChainId>;
impl Endpoint for SearchDapps {
type Response = Vec<DappWithChainId>;

fn method(&self) -> Method {
Method::GET
fn method(&self) -> Method {
Method::GET
}

fn path(&self) -> String {
"/api/v1/marketplace/dapps:search".to_string()
}

fn query(&self) -> Option<String> {
serialize_query(&self.params)
}
}

fn path(&self) -> String {
"/api/v1/marketplace/dapps:search".to_string()
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DappWithChainId {
pub dapp: Dapp,
pub chain_id: String,
}

fn query(&self) -> Option<String> {
serialize_query(&self.params)
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Dapp {
pub id: String,
pub title: String,
pub logo: String,
pub short_description: String,
}
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DappWithChainId {
pub dapp: Dapp,
pub chain_id: String,
pub mod list_categories {
use super::*;

pub struct ListCategories {}

impl Endpoint for ListCategories {
type Response = Vec<String>;

fn method(&self) -> Method {
Method::GET
}

fn path(&self) -> String {
"/api/v1/marketplace/categories".to_string()
}
}
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Dapp {
pub id: String,
pub title: String,
pub logo: String,
pub short_description: String,
pub mod list_chains {
use super::*;

pub struct ListChains {}

impl Endpoint for ListChains {
type Response = Vec<String>;

fn method(&self) -> Method {
Method::GET
}

fn path(&self) -> String {
"/api/v1/marketplace/chains".to_string()
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::ChainId;
use crate::types::ChainId;
use api_client_framework::{
serialize_query, Endpoint, Error, HttpApiClient as Client, HttpApiClientConfig,
};
Expand Down
12 changes: 2 additions & 10 deletions multichain-aggregator/multichain-aggregator-logic/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
pub mod api_key_manager;
pub mod clients;
pub mod error;
mod import;
mod proto;
pub mod repository;
pub mod search;
mod types;

pub use import::batch_import;
pub use types::{
addresses::Address, api_keys::ApiKey, batch_import_request::BatchImportRequest, chains::Chain,
token_info::Token, ChainId,
};
pub mod services;
pub mod types;
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
use super::paginate_cursor;
use crate::{
error::{ParseError, ServiceError},
error::ParseError,
types::{addresses::Address, ChainId},
};
use alloy_primitives::Address as AddressAlloy;
use entity::addresses::{ActiveModel, Column, Entity, Model};
use entity::{
addresses::{ActiveModel, Column, Entity, Model},
sea_orm_active_enums as db_enum,
};
use regex::Regex;
use sea_orm::{
prelude::Expr, sea_query::OnConflict, ActiveValue::NotSet, ColumnTrait, ConnectionTrait, DbErr,
EntityTrait, IntoSimpleExpr, Iterable, QueryFilter, QueryOrder, QuerySelect,
EntityTrait, Iterable, QueryFilter, QueryTrait,
};
use std::sync::OnceLock;

Expand Down Expand Up @@ -46,82 +50,49 @@ where
Ok(())
}

pub async fn find_by_address<C>(db: &C, address: AddressAlloy) -> Result<Vec<Address>, ServiceError>
where
C: ConnectionTrait,
{
let res = Entity::find()
.filter(Column::Hash.eq(address.as_slice()))
.all(db)
.await?
.into_iter()
.map(Address::try_from)
.collect::<Result<Vec<_>, _>>()?;

Ok(res)
}

pub async fn search_by_query<C>(db: &C, q: &str) -> Result<Vec<Address>, ServiceError>
where
C: ConnectionTrait,
{
search_by_query_paginated(db, q, None, None, 100)
.await
.map(|(addresses, _)| addresses)
}

pub async fn search_by_query_paginated<C>(
pub async fn list_addresses_paginated<C>(
db: &C,
q: &str,
address: Option<AddressAlloy>,
query: Option<String>,
chain_id: Option<ChainId>,
token_types: Option<Vec<db_enum::TokenType>>,
page_size: u64,
page_token: Option<(AddressAlloy, ChainId)>,
limit: u64,
) -> Result<(Vec<Address>, Option<(AddressAlloy, ChainId)>), ServiceError>
) -> Result<(Vec<Model>, Option<(AddressAlloy, ChainId)>), DbErr>
where
C: ConnectionTrait,
{
let page_token = page_token.unwrap_or((AddressAlloy::ZERO, ChainId::MIN));
let mut query = Entity::find()
.filter(
Expr::tuple([
Column::Hash.into_simple_expr(),
Column::ChainId.into_simple_expr(),
])
.gte(Expr::tuple([
page_token.0.as_slice().into(),
page_token.1.into(),
])),
)
.order_by_asc(Column::Hash)
.order_by_asc(Column::ChainId)
.limit(limit + 1);

if let Some(chain_id) = chain_id {
query = query.filter(Column::ChainId.eq(chain_id));
let mut c = Entity::find()
.apply_if(chain_id, |q, chain_id| {
q.filter(Column::ChainId.eq(chain_id))
})
.apply_if(address, |q, address| {
q.filter(Column::Hash.eq(address.as_slice()))
})
.apply_if(query, |q, query| {
let ts_query = prepare_ts_query(&query);
q.filter(Expr::cust_with_expr(
"to_tsvector('english', contract_name) @@ to_tsquery($1)",
ts_query,
))
})
.apply_if(token_types, |q, token_types| {
q.filter(Column::TokenType.is_in(token_types))
})
.cursor_by((Column::Hash, Column::ChainId));

if let Some(page_token) = page_token {
c.after((page_token.0.as_slice(), page_token.1));
}

let ts_query = prepare_ts_query(q);
query = query.filter(Expr::cust_with_expr(
"to_tsvector('english', contract_name) @@ to_tsquery($1) OR \
to_tsvector('english', ens_name) @@ to_tsquery($1) OR \
to_tsvector('english', token_name) @@ to_tsquery($1)",
ts_query,
));

let addresses = query
.all(db)
.await?
.into_iter()
.map(Address::try_from)
.collect::<Result<Vec<_>, _>>()?;

match addresses.get(limit as usize) {
Some(a) => Ok((
addresses[0..limit as usize].to_vec(),
Some((a.hash, a.chain_id)),
)),
None => Ok((addresses, None)),
}
paginate_cursor(db, c, page_size, |u| {
(
// unwrap is safe here because addresses are validated prior to being inserted
AddressAlloy::try_from(u.hash.as_slice()).unwrap(),
u.chain_id,
)
})
.await
}

fn non_primary_columns() -> impl Iterator<Item = Column> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::types::block_ranges::BlockRange;
use super::paginate_cursor;
use crate::types::{block_ranges::BlockRange, ChainId};
use entity::block_ranges::{ActiveModel, Column, Entity, Model};
use sea_orm::{
prelude::Expr, sea_query::OnConflict, ActiveValue::NotSet, ColumnTrait, ConnectionTrait, DbErr,
Expand Down Expand Up @@ -55,20 +56,23 @@ where
Ok(())
}

pub async fn find_matching_block_ranges<C>(
pub async fn list_matching_block_ranges_paginated<C>(
db: &C,
block_number: u64,
) -> Result<Vec<BlockRange>, DbErr>
page_size: u64,
page_token: Option<ChainId>,
) -> Result<(Vec<Model>, Option<ChainId>), DbErr>
where
C: ConnectionTrait,
{
let res = Entity::find()
let mut c = Entity::find()
.filter(Column::MinBlockNumber.lte(block_number))
.filter(Column::MaxBlockNumber.gte(block_number))
.all(db)
.await?
.into_iter()
.map(|r| r.into())
.collect();
Ok(res)
.cursor_by(Column::ChainId);

if let Some(page_token) = page_token {
c.after(page_token);
}

paginate_cursor(db, c, page_size, |u| u.chain_id).await
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,18 @@ where
Entity::insert_many(chains)
.on_conflict(
OnConflict::columns([Column::Id])
.update_columns([Column::ExplorerUrl, Column::IconUrl])
.update_columns([Column::ExplorerUrl, Column::IconUrl, Column::Name])
.value(Column::UpdatedAt, Expr::current_timestamp())
.to_owned(),
)
.exec(db)
.await?;
Ok(())
}

pub async fn list_chains<C>(db: &C) -> Result<Vec<Model>, DbErr>
where
C: ConnectionTrait,
{
Entity::find().all(db).await
}
Loading

0 comments on commit 19c7404

Please sign in to comment.