diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 0b70b3ab73..3dc06980f2 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -10,13 +10,13 @@ use { page_config::PageConfig, runes::Rune, templates::{ - BlockHtml, BlockJson, BlocksHtml, ChildrenHtml, ClockSvg, CollectionsHtml, HomeHtml, - InputHtml, InscriptionHtml, InscriptionJson, InscriptionsBlockHtml, InscriptionsHtml, - InscriptionsJson, OutputHtml, OutputJson, PageContent, PageHtml, PreviewAudioHtml, - PreviewCodeHtml, PreviewFontHtml, PreviewImageHtml, PreviewMarkdownHtml, PreviewModelHtml, - PreviewPdfHtml, PreviewTextHtml, PreviewUnknownHtml, PreviewVideoHtml, RangeHtml, RareTxt, - RuneHtml, RunesHtml, SatHtml, SatInscriptionJson, SatInscriptionsJson, SatJson, - TransactionHtml, + BlockHtml, BlockJson, BlocksHtml, ChildrenHtml, ChildrenJson, ClockSvg, CollectionsHtml, + HomeHtml, InputHtml, InscriptionHtml, InscriptionJson, InscriptionsBlockHtml, + InscriptionsHtml, InscriptionsJson, OutputHtml, OutputJson, PageContent, PageHtml, + PreviewAudioHtml, PreviewCodeHtml, PreviewFontHtml, PreviewImageHtml, PreviewMarkdownHtml, + PreviewModelHtml, PreviewPdfHtml, PreviewTextHtml, PreviewUnknownHtml, PreviewVideoHtml, + RangeHtml, RareTxt, RuneHtml, RunesHtml, SatHtml, SatInscriptionJson, SatInscriptionsJson, + SatJson, TransactionHtml, }, }, axum::{ @@ -238,6 +238,11 @@ impl Server { ) .route("/r/blockheight", get(Self::block_height)) .route("/r/blocktime", get(Self::block_time)) + .route("/r/children/:inscription_id", get(Self::children_recursive)) + .route( + "/r/children/:inscription_id/:page", + get(Self::children_recursive_paginated), + ) .route("/r/metadata/:inscription_id", get(Self::metadata)) .route("/r/sat/:sat_number", get(Self::sat_inscriptions)) .route( @@ -1352,6 +1357,28 @@ impl Server { ) } + async fn children_recursive( + Extension(index): Extension>, + Path(inscription_id): Path, + ) -> ServerResult { + Self::children_recursive_paginated(Extension(index), Path((inscription_id, 0))).await + } + + async fn children_recursive_paginated( + Extension(index): Extension>, + Path((parent, page)): Path<(InscriptionId, usize)>, + ) -> ServerResult { + let parent_sequence_number = index + .get_inscription_entry(parent)? + .ok_or_not_found(|| format!("inscription {parent}"))? + .sequence_number; + + let (ids, more) = + index.get_children_by_sequence_number_paginated(parent_sequence_number, 100, page)?; + + Ok(Json(ChildrenJson { ids, more, page }).into_response()) + } + async fn inscriptions( Extension(page_config): Extension>, Extension(index): Extension>, @@ -4496,4 +4523,76 @@ next .id .is_none()); } + + #[test] + fn children_recursive_endpoint() { + let server = TestServer::new_with_regtest_with_json_api(); + server.mine_blocks(1); + + let parent_txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, inscription("text/plain", "hello").to_witness())], + ..Default::default() + }); + + let parent_inscription_id = InscriptionId { + txid: parent_txid, + index: 0, + }; + + server.assert_response( + format!("/r/children/{parent_inscription_id}"), + StatusCode::NOT_FOUND, + &format!("inscription {parent_inscription_id} not found"), + ); + + server.mine_blocks(1); + + let children_json = + server.get_json::(format!("/r/children/{parent_inscription_id}")); + assert_eq!(children_json.ids.len(), 0); + + let mut builder = script::Builder::new(); + for _ in 0..111 { + builder = Inscription { + content_type: Some("text/plain".into()), + body: Some("hello".into()), + parent: Some(parent_inscription_id.parent_value()), + unrecognized_even_field: false, + ..Default::default() + } + .append_reveal_script_to_builder(builder); + } + + let witness = Witness::from_slice(&[builder.into_bytes(), Vec::new()]); + + let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(2, 0, 0, witness), (2, 1, 0, Default::default())], + ..Default::default() + }); + + server.mine_blocks(1); + + let first_child_inscription_id = InscriptionId { txid, index: 0 }; + let hundredth_child_inscription_id = InscriptionId { txid, index: 99 }; + let hundred_first_child_inscription_id = InscriptionId { txid, index: 100 }; + let hundred_eleventh_child_inscription_id = InscriptionId { txid, index: 110 }; + + let children_json = + server.get_json::(format!("/r/children/{parent_inscription_id}")); + + assert_eq!(children_json.ids.len(), 100); + assert_eq!(children_json.ids[0], first_child_inscription_id); + assert_eq!(children_json.ids[99], hundredth_child_inscription_id); + assert!(children_json.more); + assert_eq!(children_json.page, 0); + + let children_json = + server.get_json::(format!("/r/children/{parent_inscription_id}/1")); + + assert_eq!(children_json.ids.len(), 11); + assert_eq!(children_json.ids[0], hundred_first_child_inscription_id); + assert_eq!(children_json.ids[10], hundred_eleventh_child_inscription_id); + assert!(!children_json.more); + assert_eq!(children_json.page, 1); + } } diff --git a/src/templates.rs b/src/templates.rs index d7604dacaf..d20c71cbc3 100644 --- a/src/templates.rs +++ b/src/templates.rs @@ -3,7 +3,7 @@ use {super::*, boilerplate::Boilerplate}; pub(crate) use { block::{BlockHtml, BlockJson}, blocks::BlocksHtml, - children::ChildrenHtml, + children::{ChildrenHtml, ChildrenJson}, clock::ClockSvg, collections::CollectionsHtml, home::HomeHtml, diff --git a/src/templates/children.rs b/src/templates/children.rs index ac3621d57a..d495abdcea 100644 --- a/src/templates/children.rs +++ b/src/templates/children.rs @@ -9,6 +9,13 @@ pub(crate) struct ChildrenHtml { pub(crate) next_page: Option, } +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct ChildrenJson { + pub ids: Vec, + pub more: bool, + pub page: usize, +} + impl PageContent for ChildrenHtml { fn title(&self) -> String { format!("Inscription {} Children", self.parent_number)