diff --git a/crates/ark-metadata/src/elasticsearch_manager.rs b/crates/ark-metadata/src/elasticsearch_manager.rs new file mode 100644 index 000000000..1f9092eff --- /dev/null +++ b/crates/ark-metadata/src/elasticsearch_manager.rs @@ -0,0 +1,17 @@ +use crate::types::{RequestError, TokenMetadata}; +use async_trait::async_trait; + +#[cfg(any(test, feature = "mock"))] +use mockall::automock; + +#[cfg_attr(any(test, feature = "mock"), automock)] +#[async_trait] +pub trait ElasticsearchManager { + async fn upsert_token_metadata( + &self, + contract_address: &str, + token_id: &str, + chain_id: &str, + metadata: TokenMetadata, + ) -> Result<(), RequestError>; +} diff --git a/crates/ark-metadata/src/lib.rs b/crates/ark-metadata/src/lib.rs index a228798d1..11c4a2c33 100644 --- a/crates/ark-metadata/src/lib.rs +++ b/crates/ark-metadata/src/lib.rs @@ -1,3 +1,4 @@ +pub mod elasticsearch_manager; pub mod file_manager; pub mod metadata_manager; pub mod storage; diff --git a/crates/ark-metadata/src/metadata_manager.rs b/crates/ark-metadata/src/metadata_manager.rs index fd6883c1a..b62f66112 100644 --- a/crates/ark-metadata/src/metadata_manager.rs +++ b/crates/ark-metadata/src/metadata_manager.rs @@ -1,4 +1,5 @@ use crate::{ + elasticsearch_manager::ElasticsearchManager, file_manager::{FileInfo, FileManager}, storage::Storage, types::StorageError, @@ -18,11 +19,18 @@ use tracing::{debug, error, trace}; /// `MetadataManager` is responsible for managing metadata information related to tokens. /// It works with the underlying storage and Starknet client to fetch and update token metadata. -pub struct MetadataManager<'a, T: Storage, C: StarknetClient, F: FileManager> { +pub struct MetadataManager< + 'a, + T: Storage, + C: StarknetClient, + F: FileManager, + E: ElasticsearchManager, +> { storage: &'a T, starknet_client: &'a C, request_client: ReqwestClient, file_manager: &'a F, + elasticsearch_manager: &'a E, } pub struct MetadataMedia { @@ -38,6 +46,9 @@ pub enum MetadataError { #[error("Database operation failed: {0}")] DatabaseError(StorageError), + #[error("ElasticSearch operation failed: {0}")] + ElasticSearchError(String), + #[error("Failed to parse data: {0}")] ParsingError(String), @@ -51,14 +62,22 @@ pub enum MetadataError { EnvVarMissingError(String), } -impl<'a, T: Storage, C: StarknetClient, F: FileManager> MetadataManager<'a, T, C, F> { +impl<'a, T: Storage, C: StarknetClient, F: FileManager, E: ElasticsearchManager> + MetadataManager<'a, T, C, F, E> +{ /// Creates a new instance of `MetadataManager` with the given storage, Starknet client, and a new request client. - pub fn new(storage: &'a T, starknet_client: &'a C, file_manager: &'a F) -> Self { + pub fn new( + storage: &'a T, + starknet_client: &'a C, + file_manager: &'a F, + elasticsearch_manager: &'a E, + ) -> Self { MetadataManager { storage, starknet_client, request_client: ReqwestClient::new(), file_manager, + elasticsearch_manager, } } @@ -162,10 +181,15 @@ impl<'a, T: Storage, C: StarknetClient, F: FileManager> MetadataManager<'a, T, C } self.storage - .register_token_metadata(contract_address, token_id, chain_id, token_metadata) + .register_token_metadata(contract_address, token_id, chain_id, token_metadata.clone()) .await .map_err(MetadataError::DatabaseError)?; + self.elasticsearch_manager + .upsert_token_metadata(contract_address, token_id, chain_id, token_metadata) + .await + .map_err(|e| MetadataError::ElasticSearchError(e.to_string()))?; + Ok(()) } @@ -419,7 +443,10 @@ impl<'a, T: Storage, C: StarknetClient, F: FileManager> MetadataManager<'a, T, C mod tests { use super::*; - use crate::{file_manager::MockFileManager, storage::MockStorage, types::TokenWithoutMetadata}; + use crate::{ + elasticsearch_manager::MockElasticsearchManager, file_manager::MockFileManager, + storage::MockStorage, types::TokenWithoutMetadata, + }; use ark_starknet::client::MockStarknetClient; use mockall::predicate::*; use reqwest::header::HeaderMap; @@ -450,6 +477,7 @@ mod tests { let mut mock_client = MockStarknetClient::default(); let storage_manager = MockStorage::default(); let mock_file = MockFileManager::default(); + let mock_elasticsearch_manager = MockElasticsearchManager::default(); mock_client .expect_call_contract() @@ -464,7 +492,12 @@ mod tests { ]) }); - let mut metadata_manager = MetadataManager::new(&storage_manager, &mock_client, &mock_file); + let mut metadata_manager = MetadataManager::new( + &storage_manager, + &mock_client, + &mock_file, + &mock_elasticsearch_manager, + ); // EXECUTION: Call the function under test let result = metadata_manager @@ -483,6 +516,7 @@ mod tests { let mut mock_client = MockStarknetClient::default(); let mut mock_storage = MockStorage::default(); let mock_file = MockFileManager::default(); + let mut mock_elasticsearch_manager = MockElasticsearchManager::default(); let contract_address = "0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"; let ipfs_gateway_uri = "https://ipfs.example.com"; @@ -510,6 +544,18 @@ mod tests { }]) }); + mock_elasticsearch_manager + .expect_upsert_token_metadata() + .times(1) + .withf(move |contract_addr, token_id, chain_id, metadata| { + contract_addr + == "0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8" + && token_id == "1" + && chain_id == "0x534e5f4d41494e" + && metadata.normalized.name.is_none() + }) + .returning(|_, _, _, _| Ok(())); + mock_client .expect_call_contract() .times(1) @@ -530,7 +576,12 @@ mod tests { .with(always(), always(), always(), always()) .returning(|_, _, _, _| Ok(())); - let mut metadata_manager = MetadataManager::new(&mock_storage, &mock_client, &mock_file); + let mut metadata_manager = MetadataManager::new( + &mock_storage, + &mock_client, + &mock_file, + &mock_elasticsearch_manager, + ); // EXECUTION: Call the function under test let result = metadata_manager @@ -552,6 +603,7 @@ mod tests { // SETUP: Mocking and Initializing let mut mock_client = MockStarknetClient::default(); let mock_file = MockFileManager::default(); + let mock_elasticsearch_manager = MockElasticsearchManager::default(); let contract_address = FieldElement::ONE; let selector_name = selector!("tokenURI"); @@ -577,7 +629,12 @@ mod tests { }); let storage_manager = MockStorage::default(); - let mut metadata_manager = MetadataManager::new(&storage_manager, &mock_client, &mock_file); + let mut metadata_manager = MetadataManager::new( + &storage_manager, + &mock_client, + &mock_file, + &mock_elasticsearch_manager, + ); // EXECUTION: Call the function under test let result = metadata_manager diff --git a/crates/ark-metadata/src/types.rs b/crates/ark-metadata/src/types.rs index a9e0a077d..f837e0dec 100644 --- a/crates/ark-metadata/src/types.rs +++ b/crates/ark-metadata/src/types.rs @@ -120,3 +120,15 @@ pub struct MetadataProperty { property_type: String, description: String, } + +#[derive(Debug, thiserror::Error)] +pub enum RequestError { + #[error("Request error: {0}")] + Reqwest(String), +} + +impl From for RequestError { + fn from(error: reqwest::Error) -> Self { + RequestError::Reqwest(error.to_string()) + } +} diff --git a/packages/core/src/contracts.ts b/packages/core/src/contracts.ts index 03267bbe1..ee0f2ed19 100644 --- a/packages/core/src/contracts.ts +++ b/packages/core/src/contracts.ts @@ -1,14 +1,12 @@ // This file is auto-generated. Do not edit directly. export const SEPOLIA_CONTRACTS = { - messaging: - "0x74f13f1dffb5ad3c051d535ba03514e653b6dcac68e30b2db66a0aa0217c815", - executor: "0xb86ab357c15c12fb78f9b0a19fa974c730fcbab96f17881827dde871665f0b", - orderbook: "0x795b605fa3144afd6f11a4499f71b9cf373bcba3f1b2835d51f65ab59392261" + "messaging": "0x74f13f1dffb5ad3c051d535ba03514e653b6dcac68e30b2db66a0aa0217c815", + "executor": "0xb86ab357c15c12fb78f9b0a19fa974c730fcbab96f17881827dde871665f0b", + "orderbook": "0x795b605fa3144afd6f11a4499f71b9cf373bcba3f1b2835d51f65ab59392261" }; export const MAINNET_CONTRACTS = { - messaging: - "0x57d45cc46de463f7ae63b74ce9b6b6b496a1178b02e7ad04d7c307caa698b7b", - executor: "0x7b42945bc47001db92fe1b9739d753925263f2f1036c2ae1f87536c916ee6a", - orderbook: "0x5add3084bb8664eb2a641cf26a28f60588c3ccd63af0632aafefcbb2332c345" + "messaging": "0x57d45cc46de463f7ae63b74ce9b6b6b496a1178b02e7ad04d7c307caa698b7b", + "executor": "0x7b42945bc47001db92fe1b9739d753925263f2f1036c2ae1f87536c916ee6a", + "orderbook": "0x5add3084bb8664eb2a641cf26a28f60588c3ccd63af0632aafefcbb2332c345" };