commit 1ba826b3a1e9793bdb4a552dea39ce50a9c7d0f8
parent c04ef78acc61daffa4d416a4c79dcb928993ee0a
Author: triesap <137732411+triesap@users.noreply.github.com>
Date: Fri, 22 Aug 2025 18:38:00 -0700
Edit `events/profile` rpc, add `radroots-nostr` crate.
Diffstat:
7 files changed, 133 insertions(+), 106 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -355,6 +355,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
[[package]]
+name = "cfg_aliases"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+
+[[package]]
name = "chacha20"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -774,9 +780,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
dependencies = [
"cfg-if",
+ "js-sys",
"libc",
"r-efi",
"wasi 0.14.2+wasi-0.2.4",
+ "wasm-bindgen",
]
[[package]]
@@ -954,6 +962,7 @@ dependencies = [
"tokio",
"tokio-rustls",
"tower-service",
+ "webpki-roots 1.0.2",
]
[[package]]
@@ -990,7 +999,7 @@ dependencies = [
"libc",
"percent-encoding",
"pin-project-lite",
- "socket2",
+ "socket2 0.6.0",
"system-configuration",
"tokio",
"tower-service",
@@ -1344,6 +1353,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86ea4e65087ff52f3862caff188d489f1fab49a0cb09e01b2e3f1a617b10aaed"
[[package]]
+name = "lru-slab"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
+
+[[package]]
name = "matchers"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1783,6 +1798,61 @@ dependencies = [
]
[[package]]
+name = "quinn"
+version = "0.11.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8"
+dependencies = [
+ "bytes",
+ "cfg_aliases",
+ "pin-project-lite",
+ "quinn-proto",
+ "quinn-udp",
+ "rustc-hash",
+ "rustls",
+ "socket2 0.5.10",
+ "thiserror 2.0.16",
+ "tokio",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-proto"
+version = "0.11.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e"
+dependencies = [
+ "bytes",
+ "getrandom 0.3.3",
+ "lru-slab",
+ "rand 0.9.2",
+ "ring",
+ "rustc-hash",
+ "rustls",
+ "rustls-pki-types",
+ "slab",
+ "thiserror 2.0.16",
+ "tinyvec",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-udp"
+version = "0.5.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970"
+dependencies = [
+ "cfg_aliases",
+ "libc",
+ "once_cell",
+ "socket2 0.5.10",
+ "tracing",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1826,6 +1896,20 @@ dependencies = [
]
[[package]]
+name = "radroots-nostr"
+version = "0.1.0"
+dependencies = [
+ "nostr",
+ "nostr-sdk",
+ "radroots-events",
+ "radroots-events-codec",
+ "reqwest",
+ "serde",
+ "serde_json",
+ "thiserror 1.0.69",
+]
+
+[[package]]
name = "radroots-runtime"
version = "0.1.0"
dependencies = [
@@ -1855,6 +1939,7 @@ dependencies = [
"radroots-core",
"radroots-events",
"radroots-events-codec",
+ "radroots-nostr",
"radroots-runtime",
"reqwest",
"serde",
@@ -2001,6 +2086,8 @@ dependencies = [
"native-tls",
"percent-encoding",
"pin-project-lite",
+ "quinn",
+ "rustls",
"rustls-pki-types",
"serde",
"serde_json",
@@ -2008,6 +2095,7 @@ dependencies = [
"sync_wrapper",
"tokio",
"tokio-native-tls",
+ "tokio-rustls",
"tower",
"tower-http",
"tower-service",
@@ -2015,6 +2103,7 @@ dependencies = [
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
+ "webpki-roots 1.0.2",
]
[[package]]
@@ -2125,6 +2214,7 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
dependencies = [
+ "web-time",
"zeroize",
]
@@ -2343,6 +2433,16 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "socket2"
+version = "0.5.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "socket2"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
@@ -2579,7 +2679,7 @@ dependencies = [
"pin-project-lite",
"signal-hook-registry",
"slab",
- "socket2",
+ "socket2 0.6.0",
"tokio-macros",
"windows-sys 0.59.0",
]
@@ -3089,6 +3189,16 @@ dependencies = [
]
[[package]]
+name = "web-time"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
name = "webpki-roots"
version = "0.26.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
@@ -14,6 +14,7 @@ license = "AGPL-3.0"
radroots-core = { path = "../../crates/crates/core" }
radroots-events = { path = "../../crates/crates/events" }
radroots-events-codec = { path = "../../crates/crates/events-codec" }
+radroots-nostr = { path = "../../crates/crates/nostr" }
radroots-runtime = { path = "../../crates/crates/runtime" }
anyhow = { version = "1" }
diff --git a/crates/radrootsd/Cargo.toml b/crates/radrootsd/Cargo.toml
@@ -11,6 +11,7 @@ description = "The radroots daemon binary"
radroots-core = { workspace = true, features = ["std", "serde", "typeshare"] }
radroots-events = { workspace = true, features = ["serde"] }
radroots-events-codec = { workspace = true, features = ["nostr"] }
+radroots-nostr = { workspace = true, features = ["sdk", "codec", "http"] }
radroots-runtime = { workspace = true, features = ["cli"] }
anyhow = { workspace = true }
diff --git a/crates/radrootsd/src/infra/nostr.rs b/crates/radrootsd/src/infra/nostr.rs
@@ -1,43 +0,0 @@
-use nostr::{key::PublicKey, nips::nip19::FromBech32};
-use radroots_events::relay_document::models::RadrootsRelayDocument;
-
-use crate::{rpc::RpcError, utils::ws_to_http};
-
-#[derive(Debug, thiserror::Error)]
-pub enum NostrError {
- #[error("invalid pubkey format: {0}")]
- InvalidPubkey(String),
-}
-
-impl From<NostrError> for RpcError {
- fn from(err: NostrError) -> Self {
- RpcError::InvalidParams(err.to_string())
- }
-}
-
-pub fn parse_pubkey(s: &str) -> Result<PublicKey, NostrError> {
- PublicKey::from_bech32(s)
- .or_else(|_| PublicKey::from_hex(s))
- .map_err(|_| NostrError::InvalidPubkey(s.to_string()))
-}
-
-pub fn parse_pubkeys(input: &[String]) -> Result<Vec<PublicKey>, RpcError> {
- input
- .iter()
- .map(|s| parse_pubkey(s).map_err(Into::into))
- .collect()
-}
-
-pub async fn fetch_nip11(ws_url: &str) -> Option<RadrootsRelayDocument> {
- let http_url = ws_to_http(ws_url)?;
- let client = reqwest::Client::new();
- client
- .get(&http_url)
- .header("Accept", "application/nostr+json")
- .send()
- .await
- .ok()?
- .json::<RadrootsRelayDocument>()
- .await
- .ok()
-}
diff --git a/crates/radrootsd/src/lib.rs b/crates/radrootsd/src/lib.rs
@@ -1,12 +1,8 @@
pub mod cli;
pub mod config;
-pub mod infra {
- pub mod nostr;
-}
pub mod identity;
pub mod radrootsd;
pub mod rpc;
-pub mod utils;
use anyhow::Result;
diff --git a/crates/radrootsd/src/rpc/events/profile/mod.rs b/crates/radrootsd/src/rpc/events/profile/mod.rs
@@ -2,7 +2,6 @@ use std::time::Duration;
use anyhow::Result;
use jsonrpsee::RpcModule;
-use nostr::nips::nip19::ToBech32;
use serde::Deserialize;
use serde_json::{Value as JsonValue, json};
@@ -12,7 +11,9 @@ use crate::rpc::RpcError;
use radroots_events::profile::models::RadrootsProfile;
use radroots_events_codec::profile::encode::to_metadata;
-use nostr_sdk::prelude::EventBuilder;
+use radroots_nostr::prelude::{
+ build_metadata_event, fetch_latest_metadata_for_author, nostr_send_event, npub_string,
+};
#[derive(Debug, Deserialize)]
struct PublishProfileParams {
@@ -27,49 +28,14 @@ pub fn module(radrootsd: Radrootsd) -> Result<RpcModule<Radrootsd>> {
return Err(RpcError::NoRelays);
}
- let ctx_pk = ctx.pubkey;
+ let me_pk = ctx.pubkey;
- let filter = nostr::Filter::new()
- .authors(vec![ctx_pk])
- .kind(nostr::Kind::Metadata);
-
- let stored = ctx
- .client
- .database()
- .query(filter.clone())
- .await
- .map_err(|e| RpcError::Other(format!("database query failed: {e}")))?;
- let fetched = ctx
- .client
- .fetch_events(filter, Duration::from_secs(10))
+ let latest = fetch_latest_metadata_for_author(&ctx.client, me_pk, Duration::from_secs(10))
.await
- .map_err(|e| RpcError::Other(format!("network fetch failed: {e}")))?;
-
- let mut latest: Option<nostr::Event> = None;
-
- let mut consider = |ev: nostr::Event| {
- if ev.kind != nostr::Kind::Metadata {
- return;
- }
- if let Some(cur) = &latest {
- if ev.created_at > cur.created_at {
- latest = Some(ev);
- }
- } else {
- latest = Some(ev);
- }
- };
+ .map_err(|e| RpcError::Other(format!("metadata fetch failed: {e}")))?;
- for ev in stored.into_iter() {
- consider(ev);
- }
- for ev in fetched.into_iter() {
- consider(ev);
- }
-
- let ctx_npub = ctx_pk
- .to_bech32()
- .map_err(|e| RpcError::Other(format!("bech32 encode failed: {e}")))?;
+ let npub =
+ npub_string(&me_pk).ok_or_else(|| RpcError::Other("bech32 encode failed".into()))?;
let row = if let Some(ev) = latest {
let parsed: Option<serde_json::Value> = serde_json::from_str(&ev.content).ok();
@@ -77,8 +43,8 @@ pub fn module(radrootsd: Radrootsd) -> Result<RpcModule<Radrootsd>> {
serde_json::from_str(&ev.content).ok();
json!({
- "author_hex": ctx_pk.to_string(),
- "author_npub": ctx_npub,
+ "author_hex": me_pk.to_string(),
+ "author_npub": npub,
"event_id": ev.id.to_string(),
"created_at": ev.created_at.as_u64(),
"content": ev.content,
@@ -87,8 +53,8 @@ pub fn module(radrootsd: Radrootsd) -> Result<RpcModule<Radrootsd>> {
})
} else {
json!({
- "author_hex": ctx_pk.to_string(),
- "author_npub": ctx_npub,
+ "author_hex": me_pk.to_string(),
+ "author_npub": npub,
"event_id": null,
"created_at": null,
"content": null,
@@ -111,12 +77,9 @@ pub fn module(radrootsd: Radrootsd) -> Result<RpcModule<Radrootsd>> {
.map_err(|e| RpcError::InvalidParams(e.to_string()))?;
let metadata = to_metadata(&profile).map_err(|e| RpcError::InvalidParams(e.to_string()))?;
+ let builder = build_metadata_event(&metadata);
- let builder = EventBuilder::metadata(&metadata);
-
- let output = ctx
- .client
- .send_event_builder(builder)
+ let output = nostr_send_event(&ctx.client, builder)
.await
.map_err(|e| RpcError::Other(format!("failed to publish metadata: {e}")))?;
diff --git a/crates/radrootsd/src/rpc/relays.rs b/crates/radrootsd/src/rpc/relays.rs
@@ -3,10 +3,11 @@ use jsonrpsee::RpcModule;
use serde::Deserialize;
use serde_json::{Value as JsonValue, json};
-use crate::infra::nostr::fetch_nip11;
use crate::radrootsd::Radrootsd;
use crate::rpc::RpcError;
+use radroots_nostr::prelude::{add_relay, connect, fetch_nip11, remove_relay};
+
#[derive(Debug, Deserialize)]
struct AddParams {
url: String,
@@ -30,8 +31,7 @@ pub fn module(radrootsd: Radrootsd) -> Result<RpcModule<Radrootsd>> {
let AddParams { url } = params
.parse()
.map_err(|e| RpcError::InvalidParams(e.to_string()))?;
- ctx.client
- .add_relay(&url)
+ add_relay(&ctx.client, &url)
.await
.map_err(|e| RpcError::AddRelay(url.clone(), e.to_string()))?;
@@ -43,8 +43,7 @@ pub fn module(radrootsd: Radrootsd) -> Result<RpcModule<Radrootsd>> {
.parse()
.map_err(|e| RpcError::InvalidParams(e.to_string()))?;
- ctx.client
- .force_remove_relay(&url)
+ remove_relay(&ctx.client, &url)
.await
.map_err(|e| RpcError::Other(format!("failed to remove relay {url}: {e}")))?;
@@ -109,7 +108,7 @@ pub fn module(radrootsd: Radrootsd) -> Result<RpcModule<Radrootsd>> {
return Err(RpcError::NoRelays);
}
let client = ctx.client.clone();
- tokio::spawn(async move { client.connect().await });
+ tokio::spawn(async move { connect(&client).await });
Ok::<JsonValue, RpcError>(json!({ "connecting": relays.len() }))
})?;