From 1df7b1b37c1bea8011fe937451213aef113a5611 Mon Sep 17 00:00:00 2001 From: Jon Staab Date: Wed, 25 Feb 2026 14:08:43 -0800 Subject: [PATCH] Publish metadata on startup --- backend/README.md | 11 ++++++++- backend/src/config.rs | 14 +++++++++++ backend/src/main.rs | 10 ++++++++ backend/src/platform.rs | 53 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 backend/src/platform.rs diff --git a/backend/README.md b/backend/README.md index c18b8d7..77dcba7 100644 --- a/backend/README.md +++ b/backend/README.md @@ -37,6 +37,9 @@ Environment variables: | `RELAY_DOMAIN` | Relay base domain for subdomains | `spaces.coracle.social` | | `NWC_URL` | Platform NWC URL for invoice generation | _required for billing_ | | `NOSTR_INDEXER_RELAYS` | Comma-separated relays to fetch kind `10050` DM relays | _required for notifications_ | +| `PLATFORM_NAME` | Platform display name for kind `0` metadata | _optional_ | +| `PLATFORM_DESCRIPTION` | Platform description for kind `0` metadata | _optional_ | +| `PLATFORM_MESSAGING_RELAYS` | Comma-separated relays published in kind `10050` | _optional_ | The database directory is created automatically if it doesn’t exist. @@ -82,6 +85,13 @@ The backend runs an in-process billing loop that: - Sends NIP-17 DMs with invoices when recurring is off - Sends NIP-17 DMs on successful payment when recurring is on +On startup, the backend publishes: + +- Kind `0` metadata (name/description) +- Kind `10050` relay list for DMs + +These are published to the relays listed in `NOSTR_INDEXER_RELAYS`. + ## API Routes Tenant routes (all require NIP-98 auth; pubkey is inferred from the token): @@ -108,4 +118,3 @@ Admin routes (all require NIP-98 auth; pubkey must be in `HOSTING_ADMIN_PUBKEYS` ## Next Steps - Add invoice generation and billing jobs -- On start, publish kind 0, 10002, 10050 to indexer relays based on env vars diff --git a/backend/src/config.rs b/backend/src/config.rs index f46f4b5..a0ad3d4 100644 --- a/backend/src/config.rs +++ b/backend/src/config.rs @@ -11,6 +11,9 @@ pub struct Config { pub relay_domain: String, pub platform_nwc_url: String, pub indexer_relays: Vec, + pub platform_name: String, + pub platform_description: String, + pub platform_messaging_relays: Vec, } impl Config { @@ -40,6 +43,14 @@ impl Config { .map(|v| v.trim().to_string()) .filter(|v| !v.is_empty()) .collect::>(); + let platform_name = env::var("PLATFORM_NAME").unwrap_or_default(); + let platform_description = env::var("PLATFORM_DESCRIPTION").unwrap_or_default(); + let platform_messaging_relays = env::var("PLATFORM_MESSAGING_RELAYS") + .unwrap_or_default() + .split(',') + .map(|v| v.trim().to_string()) + .filter(|v| !v.is_empty()) + .collect::>(); Self { database_url, @@ -51,6 +62,9 @@ impl Config { relay_domain, platform_nwc_url, indexer_relays, + platform_name, + platform_description, + platform_messaging_relays, } } } diff --git a/backend/src/main.rs b/backend/src/main.rs index 84443df..b8b4718 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -5,6 +5,7 @@ mod config; mod db; mod models; mod notifications; +mod platform; mod provisioning; mod repo; @@ -20,6 +21,7 @@ use crate::config::Config; use crate::db::init_pool; use crate::billing::BillingService; use crate::notifications::Nip17Notifier; +use crate::platform::publish_platform_identity; use crate::provisioning::Provisioner; use crate::repo::Repo; @@ -35,6 +37,14 @@ async fn main() -> Result<()> { let pool = init_pool(&config.database_url).await?; let repo = Repo::new(pool); + publish_platform_identity( + &config.platform_secret, + &config.indexer_relays, + &config.platform_name, + &config.platform_description, + &config.platform_messaging_relays, + ) + .await?; let notifier = Nip17Notifier::new(config.platform_secret.clone(), config.indexer_relays.clone()).await?; let billing = BillingService::new( repo.clone(), diff --git a/backend/src/platform.rs b/backend/src/platform.rs new file mode 100644 index 0000000..833d9d6 --- /dev/null +++ b/backend/src/platform.rs @@ -0,0 +1,53 @@ +use anyhow::{anyhow, Result}; +use nostr_sdk::prelude::*; + +pub async fn publish_platform_identity( + platform_secret: &str, + indexer_relays: &[String], + name: &str, + description: &str, + messaging_relays: &[String], +) -> Result<()> { + if indexer_relays.is_empty() { + return Ok(()); + } + + if platform_secret.trim().is_empty() { + return Err(anyhow!("PLATFORM_SECRET is required for platform identity")); + } + + let keys = Keys::parse(platform_secret)?; + let client = Client::new(keys); + + for relay in indexer_relays { + client.add_relay(relay).await?; + } + + client.connect().await; + + let mut metadata = Metadata::new(); + if !name.is_empty() { + metadata = metadata.name(name); + } + if !description.is_empty() { + metadata = metadata.about(description); + } + + let metadata_builder = EventBuilder::metadata(&metadata); + client.send_event_builder(metadata_builder).await?; + + if messaging_relays.is_empty() { + return Ok(()); + } + + let mut tags = Vec::new(); + for relay in messaging_relays { + let tag = Tag::parse(["relay", relay.as_str()])?; + tags.push(tag); + } + + let relay_builder = EventBuilder::new(Kind::Custom(10050), "").tags(tags); + client.send_event_builder(relay_builder).await?; + + Ok(()) +}