radrootsd

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

commit 636d97ce68652365a6a1f4ab8c0bc6bc429393d9
parent 499ad71168a923dc36cedb03e3789c2b8aea0631
Author: triesap <triesap@radroots.dev>
Date:   Mon,  5 Jan 2026 22:47:01 +0000

events: add test event overrides and centralize publishing

- Add optional author_secret_key and created_at to publish methods
- Gate test event overrides behind allow_test_events config flag
- Centralize event sending logic in shared helper
- Update tests to use base64-compatible d tags

Diffstat:
Msrc/api/jsonrpc/methods/domains/trade/listing/helpers.rs | 4++--
Msrc/api/jsonrpc/methods/domains/trade/listing/validate.rs | 2+-
Msrc/api/jsonrpc/methods/events/farm/list.rs | 6+++---
Msrc/api/jsonrpc/methods/events/farm/publish.rs | 18+++++++++++++-----
Msrc/api/jsonrpc/methods/events/helpers.rs | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/api/jsonrpc/methods/events/list_set/publish.rs | 13+++++++++----
Msrc/api/jsonrpc/methods/events/listing/list.rs | 6+++---
Msrc/api/jsonrpc/methods/events/listing/publish.rs | 18+++++++++++++-----
Msrc/api/jsonrpc/methods/events/plot/list.rs | 8++++----
Msrc/api/jsonrpc/methods/events/plot/publish.rs | 18+++++++++++++-----
Msrc/api/jsonrpc/methods/events/post/publish.rs | 18+++++++++++++-----
Msrc/api/jsonrpc/methods/events/profile/publish.rs | 21+++++++++++++--------
Msrc/api/jsonrpc/methods/events/resource_area/list.rs | 6+++---
Msrc/api/jsonrpc/methods/events/resource_cap/list.rs | 10+++++-----
Msrc/config.rs | 3+++
Msrc/lib.rs | 7++++++-
Msrc/radrootsd.rs | 8+++++++-
17 files changed, 214 insertions(+), 57 deletions(-)

diff --git a/src/api/jsonrpc/methods/domains/trade/listing/helpers.rs b/src/api/jsonrpc/methods/domains/trade/listing/helpers.rs @@ -245,7 +245,7 @@ mod tests { #[test] fn dvm_event_view_parses_envelope_and_prefers_envelope_order_id() { let pubkey = "1bdebe7b23fccb167fc8843280b789839dfa296ae9fd86cc9769b4813d76d8a4"; - let listing_addr = format!("{LISTING_KIND}:{pubkey}:listing-1"); + let listing_addr = format!("{LISTING_KIND}:{pubkey}:AAAAAAAAAAAAAAAAAAAAAg"); let envelope = TradeListingEnvelope::new( TradeListingMessageType::OrderRequest, listing_addr, @@ -279,7 +279,7 @@ mod tests { #[test] fn order_summaries_counts_and_sorts() { let pubkey = "3bdebe7b23fccb167fc8843280b789839dfa296ae9fd86cc9769b4813d76d8a4"; - let listing_addr = format!("{LISTING_KIND}:{pubkey}:listing-1"); + let listing_addr = format!("{LISTING_KIND}:{pubkey}:AAAAAAAAAAAAAAAAAAAAAg"); let order_a = vec![vec!["d".to_string(), "order-a".to_string()]]; let order_b = vec![vec!["d".to_string(), "order-b".to_string()]]; diff --git a/src/api/jsonrpc/methods/domains/trade/listing/validate.rs b/src/api/jsonrpc/methods/domains/trade/listing/validate.rs @@ -354,7 +354,7 @@ mod tests { fn tag_has_value_matches_exact() { let tags = vec![ vec!["t".to_string(), "radroots:type:farm".to_string()], - vec!["d".to_string(), "listing-1".to_string()], + vec!["d".to_string(), "AAAAAAAAAAAAAAAAAAAAAg".to_string()], ]; assert!(tag_has_value(&tags, "t", "radroots:type:farm")); assert!(!tag_has_value(&tags, "t", "radroots:type:individual")); diff --git a/src/api/jsonrpc/methods/events/farm/list.rs b/src/api/jsonrpc/methods/events/farm/list.rs @@ -161,7 +161,7 @@ mod tests { let pubkey = "1bdebe7b23fccb167fc8843280b789839dfa296ae9fd86cc9769b4813d76d8a4"; let old_id = format!("{:064x}", 1); let new_id = format!("{:064x}", 2); - let farm = sample_farm("farm-1", "Farm One"); + let farm = sample_farm("AAAAAAAAAAAAAAAAAAAAAA", "Farm One"); let content = serde_json::to_string(&farm).expect("content"); let tags = farm_build_tags(&farm).expect("tags"); let older = farm_event(&old_id, pubkey, 100, tags.clone(), &content); @@ -179,7 +179,7 @@ mod tests { #[test] fn farm_list_uses_tag_d_when_missing_in_content() { let pubkey = "2bdebe7b23fccb167fc8843280b789839dfa296ae9fd86cc9769b4813d76d8a4"; - let farm = sample_farm("farm-1", "Farm One"); + let farm = sample_farm("AAAAAAAAAAAAAAAAAAAAAA", "Farm One"); let tags = farm_build_tags(&farm).expect("tags"); let content_farm = sample_farm("", "Farm One"); let content = serde_json::to_string(&content_farm).expect("content"); @@ -191,7 +191,7 @@ mod tests { assert_eq!(farms.len(), 1); assert_eq!(farms[0].tags, tags); let parsed = farms[0].farm.as_ref().expect("farm"); - assert_eq!(parsed.d_tag, "farm-1"); + assert_eq!(parsed.d_tag, "AAAAAAAAAAAAAAAAAAAAAA"); assert_eq!(parsed.name, "Farm One"); } } diff --git a/src/api/jsonrpc/methods/events/farm/publish.rs b/src/api/jsonrpc/methods/events/farm/publish.rs @@ -2,18 +2,23 @@ use anyhow::Result; use jsonrpsee::server::RpcModule; use serde::Deserialize; +use crate::api::jsonrpc::methods::events::helpers::send_event_with_options; use crate::api::jsonrpc::nostr::{publish_response, PublishResponse}; use crate::api::jsonrpc::{MethodRegistry, RpcContext, RpcError}; use radroots_events::farm::RadrootsFarm; use radroots_events::kinds::KIND_FARM; use radroots_events_codec::farm::encode::farm_build_tags; -use radroots_nostr::prelude::{radroots_nostr_build_event, radroots_nostr_send_event}; +use radroots_nostr::prelude::radroots_nostr_build_event; #[derive(Debug, Deserialize)] struct PublishFarmParams { farm: RadrootsFarm, #[serde(default)] tags: Option<Vec<Vec<String>>>, + #[serde(default)] + author_secret_key: Option<String>, + #[serde(default)] + created_at: Option<u64>, } pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Result<()> { @@ -24,7 +29,12 @@ pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Res return Err(RpcError::NoRelays); } - let PublishFarmParams { farm, tags } = params + let PublishFarmParams { + farm, + tags, + author_secret_key, + created_at, + } = params .parse() .map_err(|e| RpcError::InvalidParams(e.to_string()))?; @@ -39,9 +49,7 @@ pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Res let builder = radroots_nostr_build_event(KIND_FARM, content, tag_slices) .map_err(|e| RpcError::Other(format!("failed to build farm event: {e}")))?; - let output = radroots_nostr_send_event(&ctx.state.client, builder) - .await - .map_err(|e| RpcError::Other(format!("failed to publish farm: {e}")))?; + let output = send_event_with_options(&ctx, builder, author_secret_key, created_at).await?; Ok::<PublishResponse, RpcError>(publish_response(output)) })?; diff --git a/src/api/jsonrpc/methods/events/helpers.rs b/src/api/jsonrpc/methods/events/helpers.rs @@ -3,12 +3,18 @@ use std::time::Duration; use crate::api::jsonrpc::params::{parse_pubkeys, timeout_or}; -use crate::api::jsonrpc::RpcError; +use crate::api::jsonrpc::{RpcContext, RpcError}; use radroots_nostr::prelude::{ + radroots_nostr_send_event, RadrootsNostrClient, RadrootsNostrEvent, + RadrootsNostrEventBuilder, + RadrootsNostrEventId, RadrootsNostrFilter, + RadrootsNostrKeys, + RadrootsNostrOutput, RadrootsNostrPublicKey, + RadrootsNostrTimestamp, }; pub(crate) fn parse_author_or_default( @@ -35,6 +41,68 @@ pub(crate) fn require_non_empty(label: &str, value: String) -> Result<String, Rp } } +pub(crate) fn validate_test_event_options( + allow_test_events: bool, + author_secret_key: Option<String>, + created_at: Option<u64>, +) -> Result<(Option<String>, Option<u64>), RpcError> { + let has_test_options = author_secret_key.is_some() || created_at.is_some(); + if has_test_options && !allow_test_events { + return Err(RpcError::InvalidParams( + "test event overrides require config.rpc.allow_test_events = true".to_string(), + )); + } + let author_secret_key = match author_secret_key { + Some(value) => { + let value = value.trim().to_string(); + if value.is_empty() { + return Err(RpcError::InvalidParams( + "author_secret_key cannot be empty".to_string(), + )); + } + Some(value) + } + None => None, + }; + Ok((author_secret_key, created_at)) +} + +pub(crate) async fn send_event_with_options( + ctx: &RpcContext, + builder: RadrootsNostrEventBuilder, + author_secret_key: Option<String>, + created_at: Option<u64>, +) -> Result<RadrootsNostrOutput<RadrootsNostrEventId>, RpcError> { + let (author_secret_key, created_at) = validate_test_event_options( + ctx.state.allow_test_events, + author_secret_key, + created_at, + )?; + let builder = match created_at { + Some(created_at) => { + builder.custom_created_at(RadrootsNostrTimestamp::from_secs(created_at)) + } + None => builder, + }; + + if let Some(author_secret_key) = author_secret_key { + let keys = RadrootsNostrKeys::parse(&author_secret_key) + .map_err(|e| RpcError::InvalidParams(format!("invalid author_secret_key: {e}")))?; + let event = builder + .sign_with_keys(&keys) + .map_err(|e| RpcError::Other(format!("failed to sign event: {e}")))?; + ctx.state + .client + .send_event(&event) + .await + .map_err(|e| RpcError::Other(format!("failed to publish event: {e}"))) + } else { + radroots_nostr_send_event(&ctx.state.client, builder) + .await + .map_err(|e| RpcError::Other(format!("failed to publish event: {e}"))) + } +} + pub(crate) async fn fetch_latest_event( client: &RadrootsNostrClient, filter: RadrootsNostrFilter, @@ -73,7 +141,8 @@ where #[cfg(test)] mod tests { - use super::select_latest_event; + use super::{select_latest_event, validate_test_event_options}; + use crate::api::jsonrpc::RpcError; use radroots_nostr::prelude::RadrootsNostrEvent; use serde_json::json; @@ -101,4 +170,36 @@ mod tests { assert_eq!(latest.id.to_string(), newer_id); assert_eq!(latest.created_at.as_secs(), 200); } + + #[test] + fn test_event_options_require_flag() { + let err = + validate_test_event_options(false, Some("deadbeef".to_string()), None).unwrap_err(); + match err { + RpcError::InvalidParams(message) => { + assert!(message.contains("allow_test_events")); + } + _ => panic!("unexpected error type"), + } + } + + #[test] + fn test_event_options_reject_empty_secret() { + let err = validate_test_event_options(true, Some(" ".to_string()), None).unwrap_err(); + match err { + RpcError::InvalidParams(message) => { + assert!(message.contains("author_secret_key")); + } + _ => panic!("unexpected error type"), + } + } + + #[test] + fn test_event_options_pass_through() { + let (secret_key, created_at) = + validate_test_event_options(true, Some("deadbeef".to_string()), Some(42)) + .expect("options"); + assert_eq!(secret_key, Some("deadbeef".to_string())); + assert_eq!(created_at, Some(42)); + } } diff --git a/src/api/jsonrpc/methods/events/list_set/publish.rs b/src/api/jsonrpc/methods/events/list_set/publish.rs @@ -4,12 +4,13 @@ use anyhow::Result; use jsonrpsee::server::RpcModule; use serde::Deserialize; +use crate::api::jsonrpc::methods::events::helpers::send_event_with_options; use crate::api::jsonrpc::nostr::{publish_response, PublishResponse}; use crate::api::jsonrpc::{MethodRegistry, RpcContext, RpcError}; 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::encode::list_set_build_tags; -use radroots_nostr::prelude::{radroots_nostr_build_event, radroots_nostr_send_event}; +use radroots_nostr::prelude::radroots_nostr_build_event; #[derive(Debug, Deserialize)] struct PublishListSetParams { @@ -18,6 +19,10 @@ struct PublishListSetParams { kind: Option<u32>, #[serde(default)] tags: Option<Vec<Vec<String>>>, + #[serde(default)] + author_secret_key: Option<String>, + #[serde(default)] + created_at: Option<u64>, } pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Result<()> { @@ -32,6 +37,8 @@ pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Res list_set, kind, tags, + author_secret_key, + created_at, } = params .parse() .map_err(|e| RpcError::InvalidParams(e.to_string()))?; @@ -53,9 +60,7 @@ pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Res let builder = radroots_nostr_build_event(kind, content, tag_slices) .map_err(|e| RpcError::Other(format!("failed to build list_set event: {e}")))?; - let output = radroots_nostr_send_event(&ctx.state.client, builder) - .await - .map_err(|e| RpcError::Other(format!("failed to publish list_set: {e}")))?; + let output = send_event_with_options(&ctx, builder, author_secret_key, created_at).await?; Ok::<PublishResponse, RpcError>(publish_response(output)) })?; diff --git a/src/api/jsonrpc/methods/events/listing/list.rs b/src/api/jsonrpc/methods/events/listing/list.rs @@ -172,10 +172,10 @@ mod tests { display_price_unit: None, }; RadrootsListing { - d_tag: "listing-1".to_string(), + d_tag: "AAAAAAAAAAAAAAAAAAAAAg".to_string(), farm: RadrootsListingFarmRef { pubkey: farm_pubkey.to_string(), - d_tag: "farm-1".to_string(), + d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), }, product: RadrootsListingProduct { key: "coffee".to_string(), @@ -231,7 +231,7 @@ mod tests { assert_eq!(listings.len(), 1); assert_eq!(listings[0].tags, tags); let parsed = listings[0].listing.as_ref().expect("listing"); - assert_eq!(parsed.d_tag, "listing-1"); + assert_eq!(parsed.d_tag, "AAAAAAAAAAAAAAAAAAAAAg"); assert_eq!(parsed.farm.pubkey, pubkey); assert_eq!(parsed.primary_bin_id, "bin-1"); } diff --git a/src/api/jsonrpc/methods/events/listing/publish.rs b/src/api/jsonrpc/methods/events/listing/publish.rs @@ -2,11 +2,12 @@ use anyhow::Result; use jsonrpsee::server::RpcModule; use serde::Deserialize; +use crate::api::jsonrpc::methods::events::helpers::send_event_with_options; use crate::api::jsonrpc::nostr::{publish_response, PublishResponse}; use crate::api::jsonrpc::{MethodRegistry, RpcContext, RpcError}; use radroots_events::kinds::KIND_LISTING; use radroots_events::listing::RadrootsListing; -use radroots_nostr::prelude::{radroots_nostr_build_event, radroots_nostr_send_event}; +use radroots_nostr::prelude::radroots_nostr_build_event; use radroots_trade::listing::codec::listing_tags_build; #[derive(Debug, Deserialize)] @@ -14,6 +15,10 @@ struct PublishListingParams { listing: RadrootsListing, #[serde(default)] tags: Option<Vec<Vec<String>>>, + #[serde(default)] + author_secret_key: Option<String>, + #[serde(default)] + created_at: Option<u64>, } pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Result<()> { @@ -23,7 +28,12 @@ pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Res return Err(RpcError::NoRelays); } - let PublishListingParams { listing, tags } = + let PublishListingParams { + listing, + tags, + author_secret_key, + created_at, + } = params.parse().map_err(|e| RpcError::InvalidParams(e.to_string()))?; let content = serde_json::to_string(&listing) @@ -36,9 +46,7 @@ pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Res let builder = radroots_nostr_build_event(KIND_LISTING, content, tag_slices) .map_err(|e| RpcError::Other(format!("failed to build listing event: {e}")))?; - let out = radroots_nostr_send_event(&ctx.state.client, builder) - .await - .map_err(|e| RpcError::Other(format!("failed to publish listing: {e}")))?; + let out = send_event_with_options(&ctx, builder, author_secret_key, created_at).await?; Ok::<PublishResponse, RpcError>(publish_response(out)) })?; diff --git a/src/api/jsonrpc/methods/events/plot/list.rs b/src/api/jsonrpc/methods/events/plot/list.rs @@ -164,7 +164,7 @@ mod tests { let farm_pubkey = pubkey; let old_id = format!("{:064x}", 1); let new_id = format!("{:064x}", 2); - let plot = sample_plot("plot-1", "Plot One", farm_pubkey, "farm-1"); + let plot = sample_plot("AAAAAAAAAAAAAAAAAAAAAQ", "Plot One", farm_pubkey, "AAAAAAAAAAAAAAAAAAAAAA"); let content = serde_json::to_string(&plot).expect("content"); let tags = plot_build_tags(&plot).expect("tags"); let older = plot_event(&old_id, pubkey, 100, tags.clone(), &content); @@ -182,7 +182,7 @@ mod tests { #[test] fn plot_list_uses_tag_fields_when_missing_in_content() { let pubkey = "2bdebe7b23fccb167fc8843280b789839dfa296ae9fd86cc9769b4813d76d8a4"; - let plot = sample_plot("plot-1", "Plot One", pubkey, "farm-1"); + let plot = sample_plot("AAAAAAAAAAAAAAAAAAAAAQ", "Plot One", pubkey, "AAAAAAAAAAAAAAAAAAAAAA"); let tags = plot_build_tags(&plot).expect("tags"); let content_plot = sample_plot("", "Plot One", "", ""); let content = serde_json::to_string(&content_plot).expect("content"); @@ -194,8 +194,8 @@ mod tests { assert_eq!(plots.len(), 1); assert_eq!(plots[0].tags, tags); let parsed = plots[0].plot.as_ref().expect("plot"); - assert_eq!(parsed.d_tag, "plot-1"); + assert_eq!(parsed.d_tag, "AAAAAAAAAAAAAAAAAAAAAQ"); assert_eq!(parsed.farm.pubkey, pubkey); - assert_eq!(parsed.farm.d_tag, "farm-1"); + assert_eq!(parsed.farm.d_tag, "AAAAAAAAAAAAAAAAAAAAAA"); } } diff --git a/src/api/jsonrpc/methods/events/plot/publish.rs b/src/api/jsonrpc/methods/events/plot/publish.rs @@ -2,18 +2,23 @@ use anyhow::Result; use jsonrpsee::server::RpcModule; use serde::Deserialize; +use crate::api::jsonrpc::methods::events::helpers::send_event_with_options; use crate::api::jsonrpc::nostr::{publish_response, PublishResponse}; use crate::api::jsonrpc::{MethodRegistry, RpcContext, RpcError}; use radroots_events::kinds::KIND_PLOT; use radroots_events::plot::RadrootsPlot; use radroots_events_codec::plot::encode::plot_build_tags; -use radroots_nostr::prelude::{radroots_nostr_build_event, radroots_nostr_send_event}; +use radroots_nostr::prelude::radroots_nostr_build_event; #[derive(Debug, Deserialize)] struct PublishPlotParams { plot: RadrootsPlot, #[serde(default)] tags: Option<Vec<Vec<String>>>, + #[serde(default)] + author_secret_key: Option<String>, + #[serde(default)] + created_at: Option<u64>, } pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Result<()> { @@ -24,7 +29,12 @@ pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Res return Err(RpcError::NoRelays); } - let PublishPlotParams { plot, tags } = params + let PublishPlotParams { + plot, + tags, + author_secret_key, + created_at, + } = params .parse() .map_err(|e| RpcError::InvalidParams(e.to_string()))?; @@ -39,9 +49,7 @@ pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Res let builder = radroots_nostr_build_event(KIND_PLOT, content, tag_slices) .map_err(|e| RpcError::Other(format!("failed to build plot event: {e}")))?; - let output = radroots_nostr_send_event(&ctx.state.client, builder) - .await - .map_err(|e| RpcError::Other(format!("failed to publish plot: {e}")))?; + let output = send_event_with_options(&ctx, builder, author_secret_key, created_at).await?; Ok::<PublishResponse, RpcError>(publish_response(output)) })?; diff --git a/src/api/jsonrpc/methods/events/post/publish.rs b/src/api/jsonrpc/methods/events/post/publish.rs @@ -2,16 +2,21 @@ use anyhow::Result; use jsonrpsee::server::RpcModule; use serde::Deserialize; +use crate::api::jsonrpc::methods::events::helpers::send_event_with_options; use crate::api::jsonrpc::nostr::{publish_response, PublishResponse}; use crate::api::jsonrpc::{MethodRegistry, RpcContext, RpcError}; use radroots_events::kinds::KIND_POST; -use radroots_nostr::prelude::{radroots_nostr_build_event, radroots_nostr_send_event}; +use radroots_nostr::prelude::radroots_nostr_build_event; #[derive(Debug, Deserialize)] struct PublishPostParams { content: String, #[serde(default)] tags: Option<Vec<Vec<String>>>, + #[serde(default)] + author_secret_key: Option<String>, + #[serde(default)] + created_at: Option<u64>, } pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Result<()> { @@ -22,7 +27,12 @@ pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Res return Err(RpcError::NoRelays); } - let PublishPostParams { content, tags } = params + let PublishPostParams { + content, + tags, + author_secret_key, + created_at, + } = params .parse() .map_err(|e| RpcError::InvalidParams(e.to_string()))?; @@ -33,9 +43,7 @@ pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Res let builder = radroots_nostr_build_event(KIND_POST, content, tags.unwrap_or_default()) .map_err(|e| RpcError::Other(format!("failed to build note: {e}")))?; - let output = radroots_nostr_send_event(&ctx.state.client, builder) - .await - .map_err(|e| RpcError::Other(format!("failed to publish note: {e}")))?; + let output = send_event_with_options(&ctx, builder, author_secret_key, created_at).await?; Ok::<PublishResponse, RpcError>(publish_response(output)) })?; diff --git a/src/api/jsonrpc/methods/events/profile/publish.rs b/src/api/jsonrpc/methods/events/profile/publish.rs @@ -2,20 +2,22 @@ use anyhow::Result; use jsonrpsee::server::RpcModule; use serde::Deserialize; +use crate::api::jsonrpc::methods::events::helpers::send_event_with_options; use crate::api::jsonrpc::nostr::{publish_response, PublishResponse}; use crate::api::jsonrpc::{MethodRegistry, RpcContext, RpcError}; use radroots_events::profile::{RadrootsProfile, RadrootsProfileType}; use radroots_events_codec::profile::encode::to_wire_parts_with_profile_type; -use radroots_nostr::prelude::{ - radroots_nostr_build_event, - radroots_nostr_send_event, -}; +use radroots_nostr::prelude::radroots_nostr_build_event; #[derive(Debug, Deserialize)] struct PublishProfileParams { profile: RadrootsProfile, profile_type: RadrootsProfileType, + #[serde(default)] + author_secret_key: Option<String>, + #[serde(default)] + created_at: Option<u64>, } pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Result<()> { @@ -26,7 +28,12 @@ pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Res return Err(RpcError::NoRelays); } - let PublishProfileParams { profile, profile_type } = params + let PublishProfileParams { + profile, + profile_type, + author_secret_key, + created_at, + } = params .parse() .map_err(|e| RpcError::InvalidParams(e.to_string()))?; @@ -35,9 +42,7 @@ pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Res let builder = radroots_nostr_build_event(parts.kind, parts.content, parts.tags) .map_err(|e| RpcError::Other(format!("failed to build profile event: {e}")))?; - let output = radroots_nostr_send_event(&ctx.state.client, builder) - .await - .map_err(|e| RpcError::Other(format!("failed to publish metadata: {e}")))?; + let output = send_event_with_options(&ctx, builder, author_secret_key, created_at).await?; Ok::<PublishResponse, RpcError>(publish_response(output)) })?; diff --git a/src/api/jsonrpc/methods/events/resource_area/list.rs b/src/api/jsonrpc/methods/events/resource_area/list.rs @@ -211,7 +211,7 @@ mod tests { let pubkey = "1bdebe7b23fccb167fc8843280b789839dfa296ae9fd86cc9769b4813d76d8a4"; let old_id = format!("{:064x}", 1); let new_id = format!("{:064x}", 2); - let area = sample_resource_area("area-1", "Area One"); + let area = sample_resource_area("AAAAAAAAAAAAAAAAAAAAAw", "Area One"); let content = serde_json::to_string(&area).expect("content"); let tags = resource_area_build_tags(&area).expect("tags"); let older = resource_area_event(&old_id, pubkey, 100, tags.clone(), &content); @@ -229,7 +229,7 @@ mod tests { #[test] fn resource_area_list_uses_tag_d_when_missing_in_content() { let pubkey = "2bdebe7b23fccb167fc8843280b789839dfa296ae9fd86cc9769b4813d76d8a4"; - let area = sample_resource_area("area-1", "Area One"); + let area = sample_resource_area("AAAAAAAAAAAAAAAAAAAAAw", "Area One"); let tags = resource_area_build_tags(&area).expect("tags"); let content_area = sample_resource_area("", "Area One"); let content = serde_json::to_string(&content_area).expect("content"); @@ -241,7 +241,7 @@ mod tests { assert_eq!(areas.len(), 1); assert_eq!(areas[0].tags, tags); let parsed = areas[0].resource_area.as_ref().expect("area"); - assert_eq!(parsed.d_tag, "area-1"); + assert_eq!(parsed.d_tag, "AAAAAAAAAAAAAAAAAAAAAw"); assert_eq!(parsed.name, "Area One"); } } diff --git a/src/api/jsonrpc/methods/events/resource_cap/list.rs b/src/api/jsonrpc/methods/events/resource_cap/list.rs @@ -180,7 +180,7 @@ mod tests { let pubkey = "1bdebe7b23fccb167fc8843280b789839dfa296ae9fd86cc9769b4813d76d8a4"; let old_id = format!("{:064x}", 1); let new_id = format!("{:064x}", 2); - let cap = sample_cap("cap-1", pubkey, "area-1"); + let cap = sample_cap("CAAAAAAAAAAAAAAAAAAAAA", pubkey, "AAAAAAAAAAAAAAAAAAAAAw"); let content = serde_json::to_string(&cap).expect("content"); let tags = resource_harvest_cap_build_tags(&cap).expect("tags"); let older = resource_cap_event(&old_id, pubkey, 100, tags.clone(), &content); @@ -198,9 +198,9 @@ mod tests { #[test] fn resource_cap_list_uses_tag_d_when_missing_in_content() { let pubkey = "2bdebe7b23fccb167fc8843280b789839dfa296ae9fd86cc9769b4813d76d8a4"; - let cap = sample_cap("cap-1", pubkey, "area-1"); + let cap = sample_cap("CAAAAAAAAAAAAAAAAAAAAA", pubkey, "AAAAAAAAAAAAAAAAAAAAAw"); let tags = resource_harvest_cap_build_tags(&cap).expect("tags"); - let mut content_cap = sample_cap("", pubkey, "area-1"); + let mut content_cap = sample_cap("", pubkey, "AAAAAAAAAAAAAAAAAAAAAw"); content_cap.display_label = Some("display".to_string()); let content = serde_json::to_string(&content_cap).expect("content"); let id = format!("{:064x}", 3); @@ -211,8 +211,8 @@ mod tests { assert_eq!(caps.len(), 1); assert_eq!(caps[0].tags, tags); let parsed = caps[0].resource_cap.as_ref().expect("cap"); - assert_eq!(parsed.d_tag, "cap-1"); - assert_eq!(parsed.resource_area.d_tag, "area-1"); + assert_eq!(parsed.d_tag, "CAAAAAAAAAAAAAAAAAAAAA"); + assert_eq!(parsed.resource_area.d_tag, "AAAAAAAAAAAAAAAAAAAAAw"); assert_eq!(parsed.product.key, "coffee"); } } diff --git a/src/config.rs b/src/config.rs @@ -40,6 +40,8 @@ pub struct RpcConfig { #[serde(default = "default_message_buffer_capacity")] pub message_buffer_capacity: u32, #[serde(default)] + pub allow_test_events: bool, + #[serde(default)] pub batch_request_limit: Option<u32>, } @@ -52,6 +54,7 @@ impl Default for RpcConfig { max_connections: default_max_connections(), max_subscriptions_per_connection: default_max_subscriptions_per_connection(), message_buffer_capacity: default_message_buffer_capacity(), + allow_test_events: false, batch_request_limit: None, } } diff --git a/src/lib.rs b/src/lib.rs @@ -28,7 +28,8 @@ pub async fn run_radrootsd(settings: &config::Settings, args: &cli_args) -> Resu )?; let keys = identity.keys().clone(); - let radrootsd = Radrootsd::new(keys, settings.metadata.clone()); + let allow_test_events = settings.config.rpc.allow_test_events; + let radrootsd = Radrootsd::new(keys, settings.metadata.clone(), allow_test_events); for relay in settings.config.relays.iter() { radrootsd.client.add_relay(relay).await?; @@ -38,6 +39,7 @@ pub async fn run_radrootsd(settings: &config::Settings, args: &cli_args) -> Resu let client = radrootsd.client.clone(); let md = settings.metadata.clone(); let identity = identity.clone(); + let allow_test_events = allow_test_events; let has_metadata = serde_json::to_value(&md) .ok() .and_then(|v| v.as_object().cloned()) @@ -46,6 +48,9 @@ pub async fn run_radrootsd(settings: &config::Settings, args: &cli_args) -> Resu tokio::spawn(async move { client.connect().await; + if allow_test_events { + return; + } let profile_published = match radroots_nostr_publish_identity_profile_with_type( &client, diff --git a/src/radrootsd.rs b/src/radrootsd.rs @@ -14,10 +14,15 @@ pub struct Radrootsd { pub pubkey: RadrootsNostrPublicKey, pub metadata: RadrootsNostrMetadata, pub info: serde_json::Value, + pub(crate) allow_test_events: bool, } impl Radrootsd { - pub fn new(keys: RadrootsNostrKeys, metadata: RadrootsNostrMetadata) -> Self { + pub fn new( + keys: RadrootsNostrKeys, + metadata: RadrootsNostrMetadata, + allow_test_events: bool, + ) -> Self { let pubkey = keys.public_key(); let client = RadrootsNostrClient::new(keys); let info = serde_json::json!({ @@ -31,6 +36,7 @@ impl Radrootsd { pubkey, metadata, info, + allow_test_events, } } }