Skip to content

Commit

Permalink
feat(ark-metadata): add metadata_updated_at (#162)
Browse files Browse the repository at this point in the history
## Description

- Introduce `metadata_updated_at` timestamp to record the last
modification time in the Metadata property.
- Implement compatibility for the `glb` file format.
- Ensure graceful handling and avoid panicking when unable to upload
files to S3.

## What type of PR is this? (check all applicable)

- [X] 🍕 Feature (`feat:`)

## Added tests?

- [X] 👍 yes

## Added to documentation?

- [X] 🙅 no documentation needed
  • Loading branch information
remiroyc authored Oct 27, 2023
1 parent bc42f29 commit a9eaf3e
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 12 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/ark-metadata/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ starknet.workspace = true
ark-starknet.workspace = true
async-trait.workspace = true
thiserror.workspace = true
chrono = "0.4"

[dev-dependencies]
ark-starknet = { path = "../ark-starknet", features = ["mock"] }
Expand Down
32 changes: 23 additions & 9 deletions crates/ark-metadata/src/metadata_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,19 @@ impl<'a, T: Storage, C: StarknetClient, F: FileManager> MetadataManager<'a, T, C
ipfs_gateway_uri: &str,
image_timeout: Duration,
) -> Result<(), MetadataError> {
trace!(
"refresh_token_metadata(contract_address=0x{:064x}, token_id={})",
contract_address,
token_id.to_decimal(false),
);

let token_uri = self
.get_token_uri(&token_id, contract_address)
.await
.map_err(|err| MetadataError::ParsingError(err.to_string()))?;

trace!("Token URI: {}", token_uri);

let token_metadata = get_token_metadata(
&self.request_client,
token_uri.as_str(),
Expand All @@ -108,15 +116,15 @@ impl<'a, T: Storage, C: StarknetClient, F: FileManager> MetadataManager<'a, T, C
.map(|s| s.replace("ipfs://", &ipfs_url))
.unwrap_or_default();

self.fetch_token_image(
url.as_str(),
cache,
contract_address,
&token_id,
image_timeout,
)
.await
.map_err(|err| MetadataError::RequestImageError(err.to_string()))?;
let _ = self
.fetch_token_image(
url.as_str(),
cache,
contract_address,
&token_id,
image_timeout,
)
.await;
}

self.storage
Expand Down Expand Up @@ -208,6 +216,12 @@ impl<'a, T: Storage, C: StarknetClient, F: FileManager> MetadataManager<'a, T, C
let headers = response.headers().clone();
let bytes = response.bytes().await?;
let (content_type, content_length) = extract_metadata_from_headers(&headers)?;

info!(
"Image: Content-Type={}, Content-Length={}",
content_type, content_length
);

let file_ext = file_extension_from_mime_type(content_type.as_str());

debug!(
Expand Down
1 change: 1 addition & 0 deletions crates/ark-metadata/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ pub struct MetadataAttribute {
pub struct TokenMetadata {
pub normalized: NormalizedMetadata,
pub raw: String,
pub metadata_updated_at: Option<i64>,
}

#[derive(Debug, Default, Deserialize, Serialize)]
Expand Down
21 changes: 21 additions & 0 deletions crates/ark-metadata/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::types::{MetadataType, NormalizedMetadata, TokenMetadata};
use anyhow::{anyhow, Result};
use base64::{engine::general_purpose, Engine as _};
use chrono::Utc;
use reqwest::header::{HeaderMap, CONTENT_LENGTH, CONTENT_TYPE};
use reqwest::Client;
use std::time::Duration;
Expand Down Expand Up @@ -61,9 +62,12 @@ async fn fetch_metadata(
Err(_) => NormalizedMetadata::default(),
};

let now = Utc::now();

Ok(TokenMetadata {
raw: raw_metadata,
normalized: metadata,
metadata_updated_at: Some(now.timestamp()),
})
} else {
error!("Failed to get ipfs metadata. URI: {}", uri);
Expand All @@ -79,6 +83,7 @@ async fn fetch_metadata(

pub fn file_extension_from_mime_type(mime_type: &str) -> &str {
match mime_type {
"model/gltf-binary" => "glb",
"image/png" => "png",
"image/jpeg" => "jpg",
"image/gif" => "gif",
Expand Down Expand Up @@ -113,13 +118,15 @@ fn get_onchain_metadata(uri: &str) -> Result<TokenMetadata> {
Ok(TokenMetadata {
raw: decoded.to_string(),
normalized: metadata,
metadata_updated_at: None,
})
}
Some(("data:application/json", uri)) => {
let metadata = serde_json::from_str::<NormalizedMetadata>(uri)?;
Ok(TokenMetadata {
raw: uri.to_string(),
normalized: metadata,
metadata_updated_at: None,
})
}
_ => match serde_json::from_str(uri) {
Expand Down Expand Up @@ -234,4 +241,18 @@ mod tests {
"Failed to extract or parse content length"
);
}

#[tokio::test]
async fn test_fetch_metadata() {
let client = Client::new();
let uri = "https://example.com";
let request_timeout_duration = Duration::from_secs(10);

let metadata = fetch_metadata(uri, &client, request_timeout_duration).await;
assert!(metadata.is_ok());

let uri = "invalid_uri";
let metadata = fetch_metadata(uri, &client, request_timeout_duration).await;
assert!(metadata.is_err());
}
}
13 changes: 10 additions & 3 deletions examples/pontos_sqlx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,17 +82,24 @@ impl DefaultEventHandler {

#[async_trait]
impl EventHandler for DefaultEventHandler {
async fn on_terminated(&self, block_number: u64, indexation_progress: f64) {
async fn on_block_processed(&self, block_number: u64, indexation_progress: f64) {
println!(
"pontos: block processed: block_number={}, indexation_progress={}",
block_number, indexation_progress
);
}

async fn on_block_processing(&self, block_number: u64) {
async fn on_block_processing(&self, block_timestamp: u64, block_number: Option<u64>) {
// TODO: here we want to call some storage if needed from an other object.
// But it's totally unrelated to the core process, so we can do whatever we want here.
println!("pontos: processing block: block_number={}", block_number);
println!(
"pontos: processing block: block_timestamp={}, block_number={:?}",
block_timestamp, block_number
);
}

async fn on_indexation_range_completed(&self) {
println!("pontos: indexation range completed");
}

async fn on_token_registered(&self, token: TokenInfo) {
Expand Down

0 comments on commit a9eaf3e

Please sign in to comment.