radrootsd

JSON-RPC bridge for Radroots event publishing
git clone https://radroots.dev/git/radrootsd.git
Log | Files | Refs | README | LICENSE

commit 1e6faee64497ecffb76c6bc7935908ede8e65759
parent dd6ab8320d1b918b7348cbb315bf3993a5e1eaec
Author: triesap <triesap@radroots.dev>
Date:   Sun,  4 Jan 2026 00:25:56 +0000

jsonrpc: dedupe list_set fetch across multiple d tags

- Add helper to fetch events per d tag and merge results
- Deduplicate fetched events by id using HashSet
- Import RadrootsNostrClient for helper signature
- Truncate response rows to requested limit

Diffstat:
Msrc/api/jsonrpc/methods/events/list_set/list.rs | 72+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
1 file changed, 59 insertions(+), 13 deletions(-)

diff --git a/src/api/jsonrpc/methods/events/list_set/list.rs b/src/api/jsonrpc/methods/events/list_set/list.rs @@ -3,6 +3,7 @@ use anyhow::Result; use jsonrpsee::server::RpcModule; use serde::{Deserialize, Serialize}; +use std::collections::HashSet; use std::time::Duration; use crate::api::jsonrpc::nostr::{event_tags, event_view_with_tags, NostrEventView}; @@ -12,6 +13,7 @@ use radroots_events::kinds::{is_nip51_list_set_kind, KIND_LIST_SET_GENERIC}; use radroots_events::list_set::RadrootsListSet; use radroots_events_codec::list_set::decode::list_set_from_tags; use radroots_nostr::prelude::{ + RadrootsNostrClient, RadrootsNostrEvent, RadrootsNostrFilter, RadrootsNostrKind, @@ -89,6 +91,52 @@ where items } +async fn fetch_list_set_events( + client: &RadrootsNostrClient, + base_filter: RadrootsNostrFilter, + d_tags: Option<Vec<String>>, + timeout: Duration, +) -> Result<Vec<RadrootsNostrEvent>, RpcError> { + match d_tags { + Some(d_tags) if d_tags.len() > 1 => { + let mut events = Vec::new(); + let mut seen = HashSet::new(); + for d_tag in d_tags.into_iter().filter(|tag| !tag.trim().is_empty()) { + let filter = base_filter.clone().identifiers([d_tag]); + let items = client + .fetch_events(filter, timeout) + .await + .map_err(|e| RpcError::Other(format!("fetch failed: {e}")))?; + for item in items { + let id = item.id.to_string(); + if seen.insert(id) { + events.push(item); + } + } + } + Ok(events) + } + Some(d_tags) => { + let mut filter = base_filter; + if let Some(d_tag) = d_tags.into_iter().find(|tag| !tag.trim().is_empty()) { + filter = filter.identifiers([d_tag]); + } + let events = client + .fetch_events(filter, timeout) + .await + .map_err(|e| RpcError::Other(format!("fetch failed: {e}")))?; + Ok(events) + } + None => { + let events = client + .fetch_events(base_filter, timeout) + .await + .map_err(|e| RpcError::Other(format!("fetch failed: {e}")))?; + Ok(events) + } + } +} + pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Result<()> { registry.track("events.list_set.list"); m.register_async_method("events.list_set.list", |params, ctx, _| async move { @@ -120,22 +168,20 @@ pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Res filter = filter.author(ctx.state.pubkey); } - if let Some(d_tags) = d_tags { - if !d_tags.is_empty() { - filter = filter.identifiers(d_tags); - } - } - filter = apply_time_bounds(filter, since, until); - let events = ctx - .state - .client - .fetch_events(filter, Duration::from_secs(timeout_or(timeout_secs))) - .await - .map_err(|e| RpcError::Other(format!("fetch failed: {e}")))?; + let events = fetch_list_set_events( + &ctx.state.client, + filter, + d_tags, + Duration::from_secs(timeout_or(timeout_secs)), + ) + .await?; - let items = build_list_set_rows(events); + let mut items = build_list_set_rows(events); + if items.len() > limit { + items.truncate(limit); + } Ok::<ListSetListResponse, RpcError>(ListSetListResponse { list_sets: items }) })?;