Compare commits

..

1 Commits

5 changed files with 53 additions and 91 deletions
+2 -3
View File
@@ -30,6 +30,5 @@ Members:
## `async fn sync_relay(&self, relay: &Relay, is_new: bool)`
- If `is_new`, sends `POST /relay/:id` to create the relay in zooid.
- Otherwise, sends `PATCH /relay/:id` to update it.
- Includes `secret` only for relay creation (`POST`) so updates do not rotate relay identity.
- Passes relay configuration in the body including host, schema, inactive flag, info, policy, groups, management, blossom, livekit, push, and roles.
- Otherwise, sends `PUT /relay/:id` to update it.
- Passes full relay configuration in the body including host, schema, secret, inactive flag, info, policy, groups, management, blossom, livekit, push, and roles.
+2 -1
View File
@@ -85,8 +85,9 @@ A relay is a nostr relay owned by a `tenant` and hosted by the attached zooid in
Some attributes persisted to zooid via API have special handling:
- The relay's `secret` is generated once on relay creation, persisted to the zooid configuration, and isn't stored in the database. Relay updates do not resend `secret`.
- The relay's `secret` is generated once and persisted to the zooid configuration but isn't stored in the database.
- The relay's `host` is calculated based on `subdomain` + `RELAY_DOMAIN`
- The value of `inactive` is calculated based on `status`
- The relay's `livekit_*` configuration is inferred based on environment variables and `livekit_enabled`.
- The relay's `roles` are hard-coded for now.
+48 -68
View File
@@ -2,7 +2,7 @@ use anyhow::Result;
use nostr_sdk::prelude::*;
use crate::command::Command;
use crate::models::{Activity, Relay, RELAY_STATUS_DELINQUENT, RELAY_STATUS_INACTIVE};
use crate::models::{Activity, RELAY_STATUS_DELINQUENT, RELAY_STATUS_INACTIVE};
use crate::query::Query;
#[derive(Clone)]
@@ -70,7 +70,7 @@ impl Infra {
Ok(())
}
async fn sync_and_report(&self, relay: &Relay, is_new: bool) {
async fn sync_and_report(&self, relay: &crate::models::Relay, is_new: bool) {
match self.sync_relay(relay, is_new).await {
Ok(()) => {
tracing::info!(relay = %relay.id, "relay sync succeeded");
@@ -96,7 +96,7 @@ impl Infra {
Ok(auth)
}
async fn sync_relay(&self, relay: &Relay, is_new: bool) -> Result<()> {
async fn sync_relay(&self, relay: &crate::models::Relay, is_new: bool) -> Result<()> {
let client = reqwest::Client::new();
let base = self.api_url.trim_end_matches('/');
@@ -106,6 +106,8 @@ impl Infra {
format!("{}.{}", relay.subdomain, self.relay_domain)
};
let secret = Keys::generate().secret_key().to_secret_hex();
let livekit = if relay.livekit_enabled == 1 {
serde_json::json!({
"enabled": true,
@@ -117,28 +119,53 @@ impl Infra {
serde_json::json!({ "enabled": false })
};
let body = relay_sync_body(
relay,
host,
livekit,
is_new.then(|| Keys::generate().secret_key().to_secret_hex()),
);
let body = serde_json::json!({
"host": host,
"schema": relay.schema,
"secret": secret,
"inactive": relay.status == RELAY_STATUS_INACTIVE
|| relay.status == RELAY_STATUS_DELINQUENT,
"info": {
"name": relay.info_name,
"icon": relay.info_icon,
"description": relay.info_description,
"pubkey": relay.tenant,
},
"policy": {
"public_join": relay.policy_public_join == 1,
"strip_signatures": relay.policy_strip_signatures == 1,
},
"groups": { "enabled": relay.groups_enabled == 1 },
"management": { "enabled": relay.management_enabled == 1 },
"blossom": { "enabled": relay.blossom_enabled == 1 },
"livekit": livekit,
"push": { "enabled": relay.push_enabled == 1 },
"roles": {
"admin": { "can_manage": true, "can_invite": true },
"member": { "can_invite": true },
},
});
let url = format!("{}/relay/{}", base, relay.id);
let auth = self.nip98_auth(&url, zooid_sync_http_method(is_new)).await?;
let request = if is_new {
client.post(&url)
let response = if is_new {
let url = format!("{}/relay/{}", base, relay.id);
let auth = self.nip98_auth(&url, HttpMethod::POST).await?;
client
.post(&url)
.header("Authorization", auth)
.json(&body)
.send()
.await?
} else {
client.patch(&url)
let url = format!("{}/relay/{}", base, relay.id);
let auth = self.nip98_auth(&url, HttpMethod::PUT).await?;
client
.put(&url)
.header("Authorization", auth)
.json(&body)
.send()
.await?
};
let response = request
.header("Authorization", auth)
.json(&body)
.send()
.await?;
if !response.status().is_success() {
let status = response.status();
let body = response.text().await.unwrap_or_default();
@@ -148,53 +175,6 @@ impl Infra {
}
}
fn zooid_sync_http_method(is_new: bool) -> HttpMethod {
if is_new {
HttpMethod::POST
} else {
HttpMethod::PATCH
}
}
fn relay_sync_body(
relay: &Relay,
host: String,
livekit: serde_json::Value,
secret: Option<String>,
) -> serde_json::Value {
let mut body = serde_json::json!({
"host": host,
"schema": relay.schema,
"inactive": relay.status == RELAY_STATUS_INACTIVE
|| relay.status == RELAY_STATUS_DELINQUENT,
"info": {
"name": relay.info_name,
"icon": relay.info_icon,
"description": relay.info_description,
"pubkey": relay.tenant,
},
"policy": {
"public_join": relay.policy_public_join == 1,
"strip_signatures": relay.policy_strip_signatures == 1,
},
"groups": { "enabled": relay.groups_enabled == 1 },
"management": { "enabled": relay.management_enabled == 1 },
"blossom": { "enabled": relay.blossom_enabled == 1 },
"livekit": livekit,
"push": { "enabled": relay.push_enabled == 1 },
"roles": {
"admin": { "can_manage": true, "can_invite": true },
"member": { "can_invite": true },
},
});
if let (Some(secret), Some(body_obj)) = (secret, body.as_object_mut()) {
body_obj.insert("secret".to_string(), serde_json::Value::String(secret));
}
body
}
fn should_sync_relay_activity(activity_type: &str) -> bool {
matches!(
activity_type,
@@ -203,13 +203,6 @@ export default function RelayDetailCard(props: RelayDetailCardProps) {
</Show>
</div>
<Show when={r().sync_error}>
<div class="rounded-lg border border-red-200 bg-red-50 px-4 py-3">
<p class="text-sm font-semibold text-red-800">Provisioning error</p>
<p class="mt-1 text-sm text-red-700 font-mono break-all">{r().sync_error}</p>
</div>
</Show>
<hr class="border-gray-200" />
<DetailSection title="Policy">
+1 -12
View File
@@ -1,5 +1,4 @@
import { A } from "@solidjs/router"
import { Show } from "solid-js"
import type { Relay } from "@/lib/api"
type RelayListItemProps = {
@@ -20,17 +19,7 @@ export default function RelayListItem(props: RelayListItemProps) {
<p class="text-xs text-gray-500 break-all mt-1">Tenant: {props.relay.tenant}</p>
)}
</div>
<Show
when={props.relay.sync_error}
fallback={<p class="text-xs uppercase tracking-wide text-gray-500">{props.relay.status}</p>}
>
<span
class="inline-flex items-center rounded-full border border-red-200 bg-red-50 px-2.5 py-0.5 text-xs font-medium text-red-700 max-w-56 truncate"
title={props.relay.sync_error}
>
{props.relay.sync_error}
</span>
</Show>
<p class="text-xs uppercase tracking-wide text-gray-500">{props.relay.status}</p>
</div>
</A>
</li>