diff --git a/das_api/src/api/api_impl.rs b/das_api/src/api/api_impl.rs index deb934d32..dea242983 100644 --- a/das_api/src/api/api_impl.rs +++ b/das_api/src/api/api_impl.rs @@ -7,9 +7,9 @@ use digital_asset_types::{ Cursor, PageOptions, SearchAssetsQuery, }, dapi::{ - get_asset, get_asset_proofs, get_asset_signatures, get_assets, get_assets_by_authority, - get_assets_by_creator, get_assets_by_group, get_assets_by_owner, get_proof_for_asset, - search_assets, + common::create_pagination, get_asset, get_asset_proofs, get_asset_signatures, get_assets, + get_assets_by_authority, get_assets_by_creator, get_assets_by_group, get_assets_by_owner, + get_proof_for_asset, search_assets, }, rpc::{ filter::{AssetSortBy, SearchConditionType}, @@ -526,12 +526,21 @@ impl ApiContract for DasApi { mint_address, page, limit, + before, + after, + cursor, } = payload; + let page_options = self.validate_pagination(limit, page, &before, &after, &cursor, None)?; let mint_address = validate_pubkey(mint_address.clone())?; - - get_nft_editions(&self.db_connection, mint_address, limit, page) - .await - .map_err(Into::into) + let pagination = create_pagination(&page_options)?; + get_nft_editions( + &self.db_connection, + mint_address, + &pagination, + page_options.limit, + ) + .await + .map_err(Into::into) } } diff --git a/das_api/src/api/mod.rs b/das_api/src/api/mod.rs index 5003e8e68..61edd6ef5 100644 --- a/das_api/src/api/mod.rs +++ b/das_api/src/api/mod.rs @@ -153,6 +153,10 @@ pub struct GetNftEditions { pub mint_address: String, pub page: Option, pub limit: Option, + pub before: Option, + pub after: Option, + #[serde(default)] + pub cursor: Option, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)] diff --git a/digital_asset_types/src/dao/scopes/asset.rs b/digital_asset_types/src/dao/scopes/asset.rs index 5bbbddb01..939908163 100644 --- a/digital_asset_types/src/dao/scopes/asset.rs +++ b/digital_asset_types/src/dao/scopes/asset.rs @@ -566,14 +566,35 @@ pub fn get_edition_data_from_json(data: Value) -> Result Result { + let data: Edition = attachment + .data + .clone() + .ok_or(DbErr::RecordNotFound("Edition data not found".to_string())) + .map(get_edition_data_from_json)??; + + Ok(NftEdition { + mint_address: attachment + .asset_id + .clone() + .map(|id| bs58::encode(id).into_string()) + .unwrap_or("".to_string()), + edition_number: data.edition, + edition_address: bs58::encode(attachment.id.clone()).into_string(), + }) +} + pub async fn get_nft_editions( conn: &impl ConnectionTrait, mint_address: Pubkey, - limit: Option, - page: Option, + pagination: &Pagination, + limit: u64, ) -> Result { let master_edition_pubkey = MasterEdition::find_pda(&mint_address).0; + // to fetch nft editions associated with a mint we need to fetch the master edition first let master_edition = asset_v1_account_attachments::Entity::find_by_id(master_edition_pubkey.to_bytes().to_vec()) .one(conn) @@ -582,8 +603,6 @@ pub async fn get_nft_editions( "Master Edition not found".to_string(), ))?; - let limit = limit.unwrap_or(10); - let master_edition_data: MasterEdition = master_edition .data .clone() @@ -592,48 +611,60 @@ pub async fn get_nft_editions( )) .map(get_edition_data_from_json)??; - let nft_editions = asset_v1_account_attachments::Entity::find() - .filter( - asset_v1_account_attachments::Column::AttachmentType - .eq(V1AccountAttachments::Edition) - .and(asset_v1_account_attachments::Column::Data.is_not_null()) - .and(Expr::cust(&format!( - "data->>'parent' = '{}'", - master_edition_pubkey - ))), - ) - .order_by_asc(asset_v1_account_attachments::Column::SlotUpdated) - .all(conn) - .await?; + let mut stmt = asset_v1_account_attachments::Entity::find(); + + stmt = stmt.filter( + asset_v1_account_attachments::Column::AttachmentType + .eq(V1AccountAttachments::Edition) + // The data field is a JSON field that contains the edition data. + .and(asset_v1_account_attachments::Column::Data.is_not_null()) + // The parent field is a string field that contains the master edition pubkey ( mapping edition to master edition ) + .and(Expr::cust(&format!( + "data->>'parent' = '{}'", + master_edition_pubkey + ))), + ); - let nft_editions = nft_editions - .iter() - .map(|e| -> Result { - let data: Edition = e - .data - .clone() - .ok_or(DbErr::RecordNotFound("Edition data not found".to_string())) - .map(get_edition_data_from_json)??; - - Ok(NftEdition { - mint_address: e - .asset_id - .clone() - .map(|id| bs58::encode(id).into_string()) - .unwrap_or("".to_string()), - edition_number: data.edition, - edition_address: bs58::encode(e.id.clone()).into_string(), - }) - }) - .collect::, _>>()?; + let nft_editions = paginate( + pagination, + limit, + stmt, + Order::Asc, + asset_v1_account_attachments::Column::Id, + ) + .all(conn) + .await? + .into_iter() + .map(attachment_to_nft_edition) + .collect::, _>>()?; + + let (page, before, after, cursor) = match pagination { + Pagination::Keyset { before, after } => { + let bef = before.clone().and_then(|x| String::from_utf8(x).ok()); + let aft = after.clone().and_then(|x| String::from_utf8(x).ok()); + (None, bef, aft, None) + } + Pagination::Page { page } => (Some(*page as u32), None, None, None), + Pagination::Cursor(_) => { + if let Some(last_asset) = nft_editions.last() { + let cursor_str = bs58::encode(last_asset.edition_address.clone()).into_string(); + (None, None, None, Some(cursor_str)) + } else { + (None, None, None, None) + } + } + }; Ok(NftEditions { total: nft_editions.len() as u32, - limit, - page, master_edition_address: master_edition_pubkey.to_string(), supply: master_edition_data.supply, max_supply: master_edition_data.max_supply, editions: nft_editions, + limit: limit as u32, + page, + before, + after, + cursor, }) } diff --git a/digital_asset_types/src/rpc/response.rs b/digital_asset_types/src/rpc/response.rs index 7a666fc3c..9283b0098 100644 --- a/digital_asset_types/src/rpc/response.rs +++ b/digital_asset_types/src/rpc/response.rs @@ -65,11 +65,17 @@ pub struct NftEdition { pub struct NftEditions { pub total: u32, pub limit: u32, - #[serde(skip_serializing_if = "Option::is_none")] - pub page: Option, pub master_edition_address: String, pub supply: u64, pub max_supply: Option, #[serde(skip_serializing_if = "Vec::is_empty")] pub editions: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub page: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub before: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub after: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub cursor: Option, } diff --git a/integration_tests/tests/integration_tests/nft_editions_tests.rs b/integration_tests/tests/integration_tests/nft_editions_tests.rs index 476308991..61929f2f5 100644 --- a/integration_tests/tests/integration_tests/nft_editions_tests.rs +++ b/integration_tests/tests/integration_tests/nft_editions_tests.rs @@ -38,7 +38,8 @@ async fn test_get_nft_editions() { let request = r#" { - "mintAddress": "Ey2Qb8kLctbchQsMnhZs5DjY32To2QtPuXNwWvk4NosL" + "mintAddress": "Ey2Qb8kLctbchQsMnhZs5DjY32To2QtPuXNwWvk4NosL", + "limit":10 } "#;