feat(feed): operator-configurable public feed advertised via /capabilities

A read-only web feed (feed.<domain>) is a backend-operator knob: FEED_ENABLED
plus curation switches (show owner / show premium members / a featured npub
allowlist / extra relays) surface in /capabilities.feed so the feed page adapts
per instance. Reads from the relay, so validate() fail-closes FEED_ENABLED
without RELAY_ENABLED. Off by default; all public display config, no secrets.
This commit is contained in:
jwinterm
2026-07-03 16:58:22 -04:00
parent a166ef56d7
commit ef6d280412
5 changed files with 170 additions and 0 deletions
+41
View File
@@ -49,6 +49,8 @@ pub struct FeatureCapabilities {
pub nostr_relay: bool,
/// Paid premium tier for general Nostr posting to the relay. See `premium`.
pub premium_relay: bool,
/// Public curated Nostr feed for this instance. See `feed`.
pub feed: bool,
/// Tipping (parked).
pub tips: bool,
}
@@ -88,6 +90,25 @@ pub struct PremiumCapability {
pub relay_url: String,
}
/// Public curated feed details (present only when `features.feed`). A read-only
/// web feed (e.g. `feed.<domain>`) reads from the relay and shows posts per the
/// operator's curation knobs.
#[derive(Debug, Serialize, utoipa::ToSchema)]
pub struct FeedCapability {
/// The relay the feed reads from (mirrors `messaging.relay_url`).
pub relay_url: String,
/// Include the operator's own posts.
pub show_owner: bool,
/// Include premium members' posts (all general notes on the gated relay).
pub show_premium: bool,
/// The operator's npub for owner filtering + display; `null` when unset.
pub owner_npub: Option<String>,
/// Specific featured npubs to surface.
pub allowlist_npubs: Vec<String>,
/// Extra relays to also pull the allowlisted authors from.
pub extra_relays: Vec<String>,
}
/// This instance's wallet-restore (import) policy. The wallet uses it to adapt
/// its import UX — hide/grey the restore-height field under `create-only`, warn
/// when a chosen date exceeds the bound. `max_depth_days` is present only for
@@ -142,6 +163,9 @@ pub struct CapabilitiesResponse {
/// Premium tier details; present only when `features.premium_relay`.
#[serde(skip_serializing_if = "Option::is_none")]
pub premium: Option<PremiumCapability>,
/// Public curated feed details; present only when `features.feed`.
#[serde(skip_serializing_if = "Option::is_none")]
pub feed: Option<FeedCapability>,
}
/// Whether an enabled chain can actually be served (its infra secret/URL is
@@ -202,6 +226,7 @@ pub fn effective_capabilities(config: &Config) -> CapabilitiesResponse {
// presence downgrade — never advertise a relay clients can't reach).
nostr_relay: relay_advertised(config),
premium_relay: premium_advertised(config),
feed: feed_advertised(config),
tips: config.features.tips,
},
restore: RestoreCapability {
@@ -252,6 +277,17 @@ pub fn effective_capabilities(config: &Config) -> CapabilitiesResponse {
.collect(),
relay_url: config.messaging.relay.advertised_url.clone(),
}),
feed: feed_advertised(config).then(|| {
let f = &config.feed;
FeedCapability {
relay_url: config.messaging.relay.advertised_url.clone(),
show_owner: f.show_owner,
show_premium: f.show_premium,
owner_npub: (!f.owner_npub.is_empty()).then(|| f.owner_npub.clone()),
allowlist_npubs: f.allowlist_npubs.clone(),
extra_relays: f.extra_relays.clone(),
}
}),
}
}
@@ -268,6 +304,11 @@ fn premium_advertised(config: &Config) -> bool {
config.premium.enabled && relay_advertised(config)
}
/// Whether the public feed is enabled AND the relay it reads is advertised.
fn feed_advertised(config: &Config) -> bool {
config.feed.enabled && relay_advertised(config)
}
/// Describe this instance's enabled chains and features.
#[utoipa::path(
get,
+1
View File
@@ -77,6 +77,7 @@ use utoipa::OpenApi;
crate::api::capabilities::MessagingCapability,
crate::api::capabilities::PremiumCapability,
crate::api::capabilities::PremiumPlanInfo,
crate::api::capabilities::FeedCapability,
crate::api::prices::PricesResponse,
crate::api::auth::AuthResponse,
crate::api::auth::UserInfo,