commit ca3ea412753feca2346e5f03d3a2955f25a60cf6 parent 97b26654219dc098f17a3aa3d7370d8430daa8e3 Author: triesap <triesap@radroots.dev> Date: Sun, 4 Jan 2026 11:34:24 +0000 jsonrpc: add events.*.get methods for latest entity fetch - Add get handlers for farm, listing, list_set, plot, post, profile, resource_area, and resource_cap - Introduce shared helpers for author parsing, required fields, and latest-event selection - Query local DB then fetch relays with timeout to resolve most recent matching event - Expose row builders/types as pub(crate) and register new get modules in event routers Diffstat:
26 files changed, 634 insertions(+), 16 deletions(-)
diff --git a/src/api/jsonrpc/methods/events/farm/get.rs b/src/api/jsonrpc/methods/events/farm/get.rs @@ -0,0 +1,61 @@ +#![forbid(unsafe_code)] + +use anyhow::Result; +use jsonrpsee::server::RpcModule; +use serde::{Deserialize, Serialize}; + +use crate::api::jsonrpc::{MethodRegistry, RpcContext, RpcError}; +use radroots_events::kinds::KIND_FARM; +use radroots_nostr::prelude::{RadrootsNostrFilter, RadrootsNostrKind}; + +use super::list::{build_farm_rows, FarmRow}; +use crate::api::jsonrpc::methods::events::helpers::{ + fetch_latest_event, + parse_author_or_default, + require_non_empty, +}; + +#[derive(Debug, Deserialize)] +struct FarmGetParams { + d_tag: String, + #[serde(default)] + author: Option<String>, + #[serde(default)] + timeout_secs: Option<u64>, +} + +#[derive(Clone, Debug, Serialize)] +struct FarmGetResponse { + farm: Option<FarmRow>, +} + +pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Result<()> { + registry.track("events.farm.get"); + m.register_async_method("events.farm.get", |params, ctx, _| async move { + if ctx.state.client.relays().await.is_empty() { + return Err(RpcError::NoRelays); + } + + let FarmGetParams { + d_tag, + author, + timeout_secs, + } = params + .parse() + .map_err(|e| RpcError::InvalidParams(e.to_string()))?; + + let author = parse_author_or_default(author, ctx.state.pubkey)?; + let d_tag = require_non_empty("d_tag", d_tag)?; + + let filter = RadrootsNostrFilter::new() + .kind(RadrootsNostrKind::Custom(KIND_FARM as u16)) + .author(author) + .identifiers([d_tag]); + + let event = fetch_latest_event(&ctx.state.client, filter, timeout_secs).await?; + let farm = event.and_then(|event| build_farm_rows(vec![event]).into_iter().next()); + + Ok::<FarmGetResponse, RpcError>(FarmGetResponse { farm }) + })?; + Ok(()) +} diff --git a/src/api/jsonrpc/methods/events/farm/list.rs b/src/api/jsonrpc/methods/events/farm/list.rs @@ -22,7 +22,7 @@ use radroots_nostr::prelude::{ }; #[derive(Clone, Debug, Serialize)] -struct FarmRow { +pub(crate) struct FarmRow { id: String, author: String, created_at: u64, @@ -38,7 +38,7 @@ struct FarmListResponse { farms: Vec<FarmRow>, } -fn build_farm_rows<I>(events: I) -> Vec<FarmRow> +pub(crate) fn build_farm_rows<I>(events: I) -> Vec<FarmRow> where I: IntoIterator<Item = RadrootsNostrEvent>, { diff --git a/src/api/jsonrpc/methods/events/farm/mod.rs b/src/api/jsonrpc/methods/events/farm/mod.rs @@ -5,10 +5,12 @@ use crate::api::jsonrpc::{MethodRegistry, RpcContext}; pub mod publish; pub mod list; +pub mod get; pub fn module(ctx: RpcContext, registry: MethodRegistry) -> Result<RpcModule<RpcContext>> { let mut m = RpcModule::new(ctx); list::register(&mut m, ®istry)?; publish::register(&mut m, ®istry)?; + get::register(&mut m, ®istry)?; Ok(m) } diff --git a/src/api/jsonrpc/methods/events/helpers.rs b/src/api/jsonrpc/methods/events/helpers.rs @@ -0,0 +1,104 @@ +#![forbid(unsafe_code)] + +use std::time::Duration; + +use crate::api::jsonrpc::params::{parse_pubkeys, timeout_or}; +use crate::api::jsonrpc::RpcError; +use radroots_nostr::prelude::{ + RadrootsNostrClient, + RadrootsNostrEvent, + RadrootsNostrFilter, + RadrootsNostrPublicKey, +}; + +pub(crate) fn parse_author_or_default( + author: Option<String>, + default: RadrootsNostrPublicKey, +) -> Result<RadrootsNostrPublicKey, RpcError> { + match author { + Some(author) => { + let authors = vec![author]; + let mut parsed = parse_pubkeys("author", &authors)?; + parsed + .pop() + .ok_or_else(|| RpcError::InvalidParams("author cannot be empty".to_string())) + } + None => Ok(default), + } +} + +pub(crate) fn require_non_empty(label: &str, value: String) -> Result<String, RpcError> { + if value.trim().is_empty() { + Err(RpcError::InvalidParams(format!("{label} cannot be empty"))) + } else { + Ok(value) + } +} + +pub(crate) async fn fetch_latest_event( + client: &RadrootsNostrClient, + filter: RadrootsNostrFilter, + timeout_secs: Option<u64>, +) -> Result<Option<RadrootsNostrEvent>, RpcError> { + let stored = client + .database() + .query(filter.clone()) + .await + .map_err(|e| RpcError::Other(format!("query failed: {e}")))?; + let fetched = client + .fetch_events(filter, Duration::from_secs(timeout_or(timeout_secs))) + .await + .map_err(|e| RpcError::Other(format!("fetch failed: {e}")))?; + Ok(select_latest_event( + stored.into_iter().chain(fetched.into_iter()), + )) +} + +fn select_latest_event<I>(events: I) -> Option<RadrootsNostrEvent> +where + I: IntoIterator<Item = RadrootsNostrEvent>, +{ + let mut latest: Option<RadrootsNostrEvent> = None; + for event in events { + let replace = match latest.as_ref() { + Some(current) => event.created_at > current.created_at, + None => true, + }; + if replace { + latest = Some(event); + } + } + latest +} + +#[cfg(test)] +mod tests { + use super::select_latest_event; + use radroots_nostr::prelude::RadrootsNostrEvent; + use serde_json::json; + + fn event_with_created_at(id: &str, created_at: u64) -> RadrootsNostrEvent { + let sig = format!("{:0128x}", 1); + let event_json = json!({ + "id": id, + "pubkey": "1bdebe7b23fccb167fc8843280b789839dfa296ae9fd86cc9769b4813d76d8a4", + "created_at": created_at, + "kind": 1, + "tags": [], + "content": "content", + "sig": sig, + }); + serde_json::from_value(event_json).expect("event") + } + + #[test] + fn select_latest_event_picks_newest() { + let older_id = format!("{:064x}", 1); + let newer_id = format!("{:064x}", 2); + let older = event_with_created_at(&older_id, 100); + let newer = event_with_created_at(&newer_id, 200); + let latest = select_latest_event(vec![older, newer]).expect("latest"); + assert_eq!(latest.id.to_string(), newer_id); + assert_eq!(latest.created_at.as_secs(), 200); + } +} diff --git a/src/api/jsonrpc/methods/events/list_set/get.rs b/src/api/jsonrpc/methods/events/list_set/get.rs @@ -0,0 +1,73 @@ +#![forbid(unsafe_code)] + +use anyhow::Result; +use jsonrpsee::server::RpcModule; +use serde::{Deserialize, Serialize}; + +use crate::api::jsonrpc::{MethodRegistry, RpcContext, RpcError}; +use radroots_events::kinds::{is_nip51_list_set_kind, KIND_LIST_SET_GENERIC}; +use radroots_nostr::prelude::{RadrootsNostrFilter, RadrootsNostrKind}; + +use super::list::{build_list_set_rows, ListSetRow}; +use crate::api::jsonrpc::methods::events::helpers::{ + fetch_latest_event, + parse_author_or_default, + require_non_empty, +}; + +#[derive(Debug, Deserialize)] +struct ListSetGetParams { + d_tag: String, + #[serde(default)] + author: Option<String>, + #[serde(default)] + kind: Option<u32>, + #[serde(default)] + timeout_secs: Option<u64>, +} + +#[derive(Clone, Debug, Serialize)] +struct ListSetGetResponse { + list_set: Option<ListSetRow>, +} + +pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Result<()> { + registry.track("events.list_set.get"); + m.register_async_method("events.list_set.get", |params, ctx, _| async move { + if ctx.state.client.relays().await.is_empty() { + return Err(RpcError::NoRelays); + } + + let ListSetGetParams { + d_tag, + author, + kind, + timeout_secs, + } = params + .parse() + .map_err(|e| RpcError::InvalidParams(e.to_string()))?; + + let author = parse_author_or_default(author, ctx.state.pubkey)?; + let d_tag = require_non_empty("d_tag", d_tag)?; + + let kind = kind.unwrap_or(KIND_LIST_SET_GENERIC); + if !is_nip51_list_set_kind(kind) { + return Err(RpcError::InvalidParams(format!( + "invalid list_set kind: {kind}" + ))); + } + let kind = u16::try_from(kind) + .map_err(|_| RpcError::InvalidParams(format!("list_set kind out of range: {kind}")))?; + + let filter = RadrootsNostrFilter::new() + .kind(RadrootsNostrKind::Custom(kind)) + .author(author) + .identifiers([d_tag]); + + let event = fetch_latest_event(&ctx.state.client, filter, timeout_secs).await?; + let list_set = event.and_then(|event| build_list_set_rows(vec![event]).into_iter().next()); + + Ok::<ListSetGetResponse, RpcError>(ListSetGetResponse { list_set }) + })?; + Ok(()) +} diff --git a/src/api/jsonrpc/methods/events/list_set/list.rs b/src/api/jsonrpc/methods/events/list_set/list.rs @@ -20,7 +20,7 @@ use radroots_nostr::prelude::{ }; #[derive(Clone, Debug, Serialize)] -struct ListSetRow { +pub(crate) struct ListSetRow { id: String, author: String, created_at: u64, @@ -76,7 +76,7 @@ fn list_set_kinds_or(kinds: Option<Vec<u32>>) -> Result<Vec<RadrootsNostrKind>, Ok(out) } -fn build_list_set_rows<I>(events: I) -> Vec<ListSetRow> +pub(crate) fn build_list_set_rows<I>(events: I) -> Vec<ListSetRow> where I: IntoIterator<Item = RadrootsNostrEvent>, { diff --git a/src/api/jsonrpc/methods/events/list_set/mod.rs b/src/api/jsonrpc/methods/events/list_set/mod.rs @@ -7,10 +7,12 @@ use crate::api::jsonrpc::{MethodRegistry, RpcContext}; pub mod list; pub mod publish; +pub mod get; pub fn module(ctx: RpcContext, registry: MethodRegistry) -> Result<RpcModule<RpcContext>> { let mut m = RpcModule::new(ctx); list::register(&mut m, ®istry)?; publish::register(&mut m, ®istry)?; + get::register(&mut m, ®istry)?; Ok(m) } diff --git a/src/api/jsonrpc/methods/events/listing/get.rs b/src/api/jsonrpc/methods/events/listing/get.rs @@ -0,0 +1,61 @@ +#![forbid(unsafe_code)] + +use anyhow::Result; +use jsonrpsee::server::RpcModule; +use serde::{Deserialize, Serialize}; + +use crate::api::jsonrpc::{MethodRegistry, RpcContext, RpcError}; +use radroots_events::kinds::KIND_LISTING; +use radroots_nostr::prelude::{RadrootsNostrFilter, RadrootsNostrKind}; + +use super::list::{build_listing_rows, ListingRow}; +use crate::api::jsonrpc::methods::events::helpers::{ + fetch_latest_event, + parse_author_or_default, + require_non_empty, +}; + +#[derive(Debug, Deserialize)] +struct ListingGetParams { + d_tag: String, + #[serde(default)] + author: Option<String>, + #[serde(default)] + timeout_secs: Option<u64>, +} + +#[derive(Clone, Debug, Serialize)] +struct ListingGetResponse { + listing: Option<ListingRow>, +} + +pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Result<()> { + registry.track("events.listing.get"); + m.register_async_method("events.listing.get", |params, ctx, _| async move { + if ctx.state.client.relays().await.is_empty() { + return Err(RpcError::NoRelays); + } + + let ListingGetParams { + d_tag, + author, + timeout_secs, + } = params + .parse() + .map_err(|e| RpcError::InvalidParams(e.to_string()))?; + + let author = parse_author_or_default(author, ctx.state.pubkey)?; + let d_tag = require_non_empty("d_tag", d_tag)?; + + let filter = RadrootsNostrFilter::new() + .kind(RadrootsNostrKind::Custom(KIND_LISTING as u16)) + .author(author) + .identifiers([d_tag]); + + let event = fetch_latest_event(&ctx.state.client, filter, timeout_secs).await?; + let listing = event.and_then(|event| build_listing_rows(vec![event]).into_iter().next()); + + Ok::<ListingGetResponse, RpcError>(ListingGetResponse { listing }) + })?; + Ok(()) +} diff --git a/src/api/jsonrpc/methods/events/listing/list.rs b/src/api/jsonrpc/methods/events/listing/list.rs @@ -22,7 +22,7 @@ use radroots_nostr::prelude::{ use radroots_trade::listing::codec::listing_from_event_parts; #[derive(Clone, Debug, Serialize)] -struct ListingRow { +pub(crate) struct ListingRow { id: String, author: String, created_at: u64, @@ -38,7 +38,7 @@ struct ListingListResponse { listings: Vec<ListingRow>, } -fn build_listing_rows<I>(events: I) -> Vec<ListingRow> +pub(crate) fn build_listing_rows<I>(events: I) -> Vec<ListingRow> where I: IntoIterator<Item = RadrootsNostrEvent>, { diff --git a/src/api/jsonrpc/methods/events/listing/mod.rs b/src/api/jsonrpc/methods/events/listing/mod.rs @@ -5,10 +5,12 @@ use crate::api::jsonrpc::{MethodRegistry, RpcContext}; pub mod list; pub mod publish; +pub mod get; pub fn module(ctx: RpcContext, registry: MethodRegistry) -> Result<RpcModule<RpcContext>> { let mut m = RpcModule::new(ctx); list::register(&mut m, ®istry)?; publish::register(&mut m, ®istry)?; + get::register(&mut m, ®istry)?; Ok(m) } diff --git a/src/api/jsonrpc/methods/events/mod.rs b/src/api/jsonrpc/methods/events/mod.rs @@ -6,3 +6,4 @@ pub mod listing; pub mod list_set; pub mod post; pub mod profile; +pub mod helpers; diff --git a/src/api/jsonrpc/methods/events/plot/get.rs b/src/api/jsonrpc/methods/events/plot/get.rs @@ -0,0 +1,61 @@ +#![forbid(unsafe_code)] + +use anyhow::Result; +use jsonrpsee::server::RpcModule; +use serde::{Deserialize, Serialize}; + +use crate::api::jsonrpc::{MethodRegistry, RpcContext, RpcError}; +use radroots_events::kinds::KIND_PLOT; +use radroots_nostr::prelude::{RadrootsNostrFilter, RadrootsNostrKind}; + +use super::list::{build_plot_rows, PlotRow}; +use crate::api::jsonrpc::methods::events::helpers::{ + fetch_latest_event, + parse_author_or_default, + require_non_empty, +}; + +#[derive(Debug, Deserialize)] +struct PlotGetParams { + d_tag: String, + #[serde(default)] + author: Option<String>, + #[serde(default)] + timeout_secs: Option<u64>, +} + +#[derive(Clone, Debug, Serialize)] +struct PlotGetResponse { + plot: Option<PlotRow>, +} + +pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Result<()> { + registry.track("events.plot.get"); + m.register_async_method("events.plot.get", |params, ctx, _| async move { + if ctx.state.client.relays().await.is_empty() { + return Err(RpcError::NoRelays); + } + + let PlotGetParams { + d_tag, + author, + timeout_secs, + } = params + .parse() + .map_err(|e| RpcError::InvalidParams(e.to_string()))?; + + let author = parse_author_or_default(author, ctx.state.pubkey)?; + let d_tag = require_non_empty("d_tag", d_tag)?; + + let filter = RadrootsNostrFilter::new() + .kind(RadrootsNostrKind::Custom(KIND_PLOT as u16)) + .author(author) + .identifiers([d_tag]); + + let event = fetch_latest_event(&ctx.state.client, filter, timeout_secs).await?; + let plot = event.and_then(|event| build_plot_rows(vec![event]).into_iter().next()); + + Ok::<PlotGetResponse, RpcError>(PlotGetResponse { plot }) + })?; + Ok(()) +} diff --git a/src/api/jsonrpc/methods/events/plot/list.rs b/src/api/jsonrpc/methods/events/plot/list.rs @@ -22,7 +22,7 @@ use radroots_nostr::prelude::{ }; #[derive(Clone, Debug, Serialize)] -struct PlotRow { +pub(crate) struct PlotRow { id: String, author: String, created_at: u64, @@ -38,7 +38,7 @@ struct PlotListResponse { plots: Vec<PlotRow>, } -fn build_plot_rows<I>(events: I) -> Vec<PlotRow> +pub(crate) fn build_plot_rows<I>(events: I) -> Vec<PlotRow> where I: IntoIterator<Item = RadrootsNostrEvent>, { diff --git a/src/api/jsonrpc/methods/events/plot/mod.rs b/src/api/jsonrpc/methods/events/plot/mod.rs @@ -5,10 +5,12 @@ use crate::api::jsonrpc::{MethodRegistry, RpcContext}; pub mod list; pub mod publish; +pub mod get; pub fn module(ctx: RpcContext, registry: MethodRegistry) -> Result<RpcModule<RpcContext>> { let mut m = RpcModule::new(ctx); list::register(&mut m, ®istry)?; publish::register(&mut m, ®istry)?; + get::register(&mut m, ®istry)?; Ok(m) } diff --git a/src/api/jsonrpc/methods/events/post/get.rs b/src/api/jsonrpc/methods/events/post/get.rs @@ -0,0 +1,57 @@ +#![forbid(unsafe_code)] + +use anyhow::Result; +use jsonrpsee::server::RpcModule; +use serde::{Deserialize, Serialize}; + +use crate::api::jsonrpc::{MethodRegistry, RpcContext, RpcError}; +use radroots_nostr::prelude::{ + RadrootsNostrEventId, + RadrootsNostrFilter, + RadrootsNostrKind, +}; + +use super::list::{build_post_rows, PostRow}; +use crate::api::jsonrpc::methods::events::helpers::{ + fetch_latest_event, + require_non_empty, +}; + +#[derive(Debug, Deserialize)] +struct PostGetParams { + id: String, + #[serde(default)] + timeout_secs: Option<u64>, +} + +#[derive(Clone, Debug, Serialize)] +struct PostGetResponse { + post: Option<PostRow>, +} + +pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Result<()> { + registry.track("events.post.get"); + m.register_async_method("events.post.get", |params, ctx, _| async move { + if ctx.state.client.relays().await.is_empty() { + return Err(RpcError::NoRelays); + } + + let PostGetParams { id, timeout_secs } = params + .parse() + .map_err(|e| RpcError::InvalidParams(e.to_string()))?; + + let id = require_non_empty("id", id)?; + let event_id = RadrootsNostrEventId::parse(id) + .map_err(|e| RpcError::InvalidParams(format!("invalid id: {e}")))?; + + let filter = RadrootsNostrFilter::new() + .kind(RadrootsNostrKind::TextNote) + .id(event_id); + + let event = fetch_latest_event(&ctx.state.client, filter, timeout_secs).await?; + let post = event.and_then(|event| build_post_rows(vec![event]).into_iter().next()); + + Ok::<PostGetResponse, RpcError>(PostGetResponse { post }) + })?; + Ok(()) +} diff --git a/src/api/jsonrpc/methods/events/post/list.rs b/src/api/jsonrpc/methods/events/post/list.rs @@ -21,7 +21,7 @@ use radroots_nostr::prelude::{ }; #[derive(Clone, Debug, Serialize)] -struct PostRow { +pub(crate) struct PostRow { id: String, author: String, created_at: u64, @@ -37,7 +37,7 @@ struct PostListResponse { posts: Vec<PostRow>, } -fn build_post_rows<I>(events: I) -> Vec<PostRow> +pub(crate) fn build_post_rows<I>(events: I) -> Vec<PostRow> where I: IntoIterator<Item = RadrootsNostrEvent>, { diff --git a/src/api/jsonrpc/methods/events/post/mod.rs b/src/api/jsonrpc/methods/events/post/mod.rs @@ -5,10 +5,12 @@ use crate::api::jsonrpc::{MethodRegistry, RpcContext}; pub mod list; pub mod publish; +pub mod get; pub fn module(ctx: RpcContext, registry: MethodRegistry) -> Result<RpcModule<RpcContext>> { let mut m = RpcModule::new(ctx); list::register(&mut m, ®istry)?; publish::register(&mut m, ®istry)?; + get::register(&mut m, ®istry)?; Ok(m) } diff --git a/src/api/jsonrpc/methods/events/profile/get.rs b/src/api/jsonrpc/methods/events/profile/get.rs @@ -0,0 +1,60 @@ +#![forbid(unsafe_code)] + +use anyhow::Result; +use jsonrpsee::server::RpcModule; +use serde::{Deserialize, Serialize}; + +use crate::api::jsonrpc::{MethodRegistry, RpcContext, RpcError}; +use radroots_nostr::prelude::{RadrootsNostrFilter, RadrootsNostrKind}; + +use super::list::{build_profile_rows, ProfileListRow}; +use crate::api::jsonrpc::methods::events::helpers::{ + fetch_latest_event, + parse_author_or_default, +}; + +#[derive(Debug, Deserialize)] +struct ProfileGetParams { + #[serde(default)] + author: Option<String>, + #[serde(default)] + timeout_secs: Option<u64>, +} + +#[derive(Clone, Debug, Serialize)] +struct ProfileGetResponse { + profile: Option<ProfileListRow>, +} + +pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Result<()> { + registry.track("events.profile.get"); + m.register_async_method("events.profile.get", |params, ctx, _| async move { + if ctx.state.client.relays().await.is_empty() { + return Err(RpcError::NoRelays); + } + + let ProfileGetParams { + author, + timeout_secs, + } = params + .parse() + .map_err(|e| RpcError::InvalidParams(e.to_string()))?; + + let author = parse_author_or_default(author, ctx.state.pubkey)?; + + let filter = RadrootsNostrFilter::new() + .kind(RadrootsNostrKind::Metadata) + .author(author); + + let event = fetch_latest_event(&ctx.state.client, filter, timeout_secs).await?; + let profile = match event { + Some(event) => build_profile_rows(vec![author], vec![event])? + .into_iter() + .next(), + None => None, + }; + + Ok::<ProfileGetResponse, RpcError>(ProfileGetResponse { profile }) + })?; + Ok(()) +} diff --git a/src/api/jsonrpc/methods/events/profile/list.rs b/src/api/jsonrpc/methods/events/profile/list.rs @@ -23,7 +23,7 @@ use radroots_nostr::prelude::{ }; #[derive(Clone, Debug, Serialize)] -struct ProfileListRow { +pub(crate) struct ProfileListRow { author_hex: String, author_npub: String, event_id: Option<String>, @@ -38,7 +38,7 @@ struct ProfileListResponse { profiles: Vec<ProfileListRow>, } -fn build_profile_rows<I>( +pub(crate) fn build_profile_rows<I>( authors: Vec<RadrootsNostrPublicKey>, events: I, ) -> Result<Vec<ProfileListRow>, RpcError> diff --git a/src/api/jsonrpc/methods/events/profile/mod.rs b/src/api/jsonrpc/methods/events/profile/mod.rs @@ -5,10 +5,12 @@ use crate::api::jsonrpc::{MethodRegistry, RpcContext}; pub mod list; pub mod publish; +pub mod get; pub fn module(ctx: RpcContext, registry: MethodRegistry) -> Result<RpcModule<RpcContext>> { let mut m = RpcModule::new(ctx); list::register(&mut m, ®istry)?; publish::register(&mut m, ®istry)?; + get::register(&mut m, ®istry)?; Ok(m) } diff --git a/src/api/jsonrpc/methods/events/resource_area/get.rs b/src/api/jsonrpc/methods/events/resource_area/get.rs @@ -0,0 +1,62 @@ +#![forbid(unsafe_code)] + +use anyhow::Result; +use jsonrpsee::server::RpcModule; +use serde::{Deserialize, Serialize}; + +use crate::api::jsonrpc::{MethodRegistry, RpcContext, RpcError}; +use radroots_events::kinds::KIND_RESOURCE_AREA; +use radroots_nostr::prelude::{RadrootsNostrFilter, RadrootsNostrKind}; + +use super::list::{build_resource_area_rows, ResourceAreaRow}; +use crate::api::jsonrpc::methods::events::helpers::{ + fetch_latest_event, + parse_author_or_default, + require_non_empty, +}; + +#[derive(Debug, Deserialize)] +struct ResourceAreaGetParams { + d_tag: String, + #[serde(default)] + author: Option<String>, + #[serde(default)] + timeout_secs: Option<u64>, +} + +#[derive(Clone, Debug, Serialize)] +struct ResourceAreaGetResponse { + resource_area: Option<ResourceAreaRow>, +} + +pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Result<()> { + registry.track("events.resource_area.get"); + m.register_async_method("events.resource_area.get", |params, ctx, _| async move { + if ctx.state.client.relays().await.is_empty() { + return Err(RpcError::NoRelays); + } + + let ResourceAreaGetParams { + d_tag, + author, + timeout_secs, + } = params + .parse() + .map_err(|e| RpcError::InvalidParams(e.to_string()))?; + + let author = parse_author_or_default(author, ctx.state.pubkey)?; + let d_tag = require_non_empty("d_tag", d_tag)?; + + let filter = RadrootsNostrFilter::new() + .kind(RadrootsNostrKind::Custom(KIND_RESOURCE_AREA as u16)) + .author(author) + .identifiers([d_tag]); + + let event = fetch_latest_event(&ctx.state.client, filter, timeout_secs).await?; + let resource_area = + event.and_then(|event| build_resource_area_rows(vec![event]).into_iter().next()); + + Ok::<ResourceAreaGetResponse, RpcError>(ResourceAreaGetResponse { resource_area }) + })?; + Ok(()) +} diff --git a/src/api/jsonrpc/methods/events/resource_area/list.rs b/src/api/jsonrpc/methods/events/resource_area/list.rs @@ -22,7 +22,7 @@ use radroots_nostr::prelude::{ }; #[derive(Clone, Debug, Serialize)] -struct ResourceAreaRow { +pub(crate) struct ResourceAreaRow { id: String, author: String, created_at: u64, @@ -38,7 +38,7 @@ struct ResourceAreaListResponse { resource_areas: Vec<ResourceAreaRow>, } -fn build_resource_area_rows<I>(events: I) -> Vec<ResourceAreaRow> +pub(crate) fn build_resource_area_rows<I>(events: I) -> Vec<ResourceAreaRow> where I: IntoIterator<Item = RadrootsNostrEvent>, { diff --git a/src/api/jsonrpc/methods/events/resource_area/mod.rs b/src/api/jsonrpc/methods/events/resource_area/mod.rs @@ -5,10 +5,12 @@ use crate::api::jsonrpc::{MethodRegistry, RpcContext}; pub mod list; pub mod publish; +pub mod get; pub fn module(ctx: RpcContext, registry: MethodRegistry) -> Result<RpcModule<RpcContext>> { let mut m = RpcModule::new(ctx); list::register(&mut m, ®istry)?; publish::register(&mut m, ®istry)?; + get::register(&mut m, ®istry)?; Ok(m) } diff --git a/src/api/jsonrpc/methods/events/resource_cap/get.rs b/src/api/jsonrpc/methods/events/resource_cap/get.rs @@ -0,0 +1,62 @@ +#![forbid(unsafe_code)] + +use anyhow::Result; +use jsonrpsee::server::RpcModule; +use serde::{Deserialize, Serialize}; + +use crate::api::jsonrpc::{MethodRegistry, RpcContext, RpcError}; +use radroots_events::kinds::KIND_RESOURCE_HARVEST_CAP; +use radroots_nostr::prelude::{RadrootsNostrFilter, RadrootsNostrKind}; + +use super::list::{build_resource_cap_rows, ResourceCapRow}; +use crate::api::jsonrpc::methods::events::helpers::{ + fetch_latest_event, + parse_author_or_default, + require_non_empty, +}; + +#[derive(Debug, Deserialize)] +struct ResourceCapGetParams { + d_tag: String, + #[serde(default)] + author: Option<String>, + #[serde(default)] + timeout_secs: Option<u64>, +} + +#[derive(Clone, Debug, Serialize)] +struct ResourceCapGetResponse { + resource_cap: Option<ResourceCapRow>, +} + +pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Result<()> { + registry.track("events.resource_cap.get"); + m.register_async_method("events.resource_cap.get", |params, ctx, _| async move { + if ctx.state.client.relays().await.is_empty() { + return Err(RpcError::NoRelays); + } + + let ResourceCapGetParams { + d_tag, + author, + timeout_secs, + } = params + .parse() + .map_err(|e| RpcError::InvalidParams(e.to_string()))?; + + let author = parse_author_or_default(author, ctx.state.pubkey)?; + let d_tag = require_non_empty("d_tag", d_tag)?; + + let filter = RadrootsNostrFilter::new() + .kind(RadrootsNostrKind::Custom(KIND_RESOURCE_HARVEST_CAP as u16)) + .author(author) + .identifiers([d_tag]); + + let event = fetch_latest_event(&ctx.state.client, filter, timeout_secs).await?; + let resource_cap = + event.and_then(|event| build_resource_cap_rows(vec![event]).into_iter().next()); + + Ok::<ResourceCapGetResponse, RpcError>(ResourceCapGetResponse { resource_cap }) + })?; + Ok(()) +} diff --git a/src/api/jsonrpc/methods/events/resource_cap/list.rs b/src/api/jsonrpc/methods/events/resource_cap/list.rs @@ -22,7 +22,7 @@ use radroots_nostr::prelude::{ }; #[derive(Clone, Debug, Serialize)] -struct ResourceCapRow { +pub(crate) struct ResourceCapRow { id: String, author: String, created_at: u64, @@ -38,7 +38,7 @@ struct ResourceCapListResponse { resource_caps: Vec<ResourceCapRow>, } -fn build_resource_cap_rows<I>(events: I) -> Vec<ResourceCapRow> +pub(crate) fn build_resource_cap_rows<I>(events: I) -> Vec<ResourceCapRow> where I: IntoIterator<Item = RadrootsNostrEvent>, { diff --git a/src/api/jsonrpc/methods/events/resource_cap/mod.rs b/src/api/jsonrpc/methods/events/resource_cap/mod.rs @@ -5,10 +5,12 @@ use crate::api::jsonrpc::{MethodRegistry, RpcContext}; pub mod list; pub mod publish; +pub mod get; pub fn module(ctx: RpcContext, registry: MethodRegistry) -> Result<RpcModule<RpcContext>> { let mut m = RpcModule::new(ctx); list::register(&mut m, ®istry)?; publish::register(&mut m, ®istry)?; + get::register(&mut m, ®istry)?; Ok(m) }