forked from coracle/caravel
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c9f9e3d19f |
+2
-2
@@ -70,9 +70,9 @@ Public exceptions:
|
|||||||
- `GET /plans/:id`
|
- `GET /plans/:id`
|
||||||
- `POST /stripe/webhook` (validated with Stripe signatures)
|
- `POST /stripe/webhook` (validated with Stripe signatures)
|
||||||
|
|
||||||
- `GET /identity` — get auth identity (`pubkey`, `is_admin`); side-effect-free
|
- `GET /identity` — get auth identity (`pubkey`, `is_admin`)
|
||||||
- `GET /tenants` — list tenants (admin)
|
- `GET /tenants` — list tenants (admin)
|
||||||
- `POST /tenants` — idempotently ensure a tenant row exists for the current auth pubkey (creates Stripe customer + tenant on first call, returns existing tenant otherwise)
|
- `POST /tenants` — create current auth pubkey as tenant
|
||||||
- `GET /tenants/:pubkey` — get tenant (admin or same tenant)
|
- `GET /tenants/:pubkey` — get tenant (admin or same tenant)
|
||||||
- `PUT /tenants/:pubkey/billing` — update tenant `nwc_url` (admin or same tenant)
|
- `PUT /tenants/:pubkey/billing` — update tenant `nwc_url` (admin or same tenant)
|
||||||
- `GET /relays` — list relays (`?tenant=<pubkey>` allowed for admin only)
|
- `GET /relays` — list relays (`?tenant=<pubkey>` allowed for admin only)
|
||||||
|
|||||||
+4
-15
@@ -46,8 +46,9 @@ Notes:
|
|||||||
|
|
||||||
- Serves `GET /identity`
|
- Serves `GET /identity`
|
||||||
- Authorizes anyone, but must be authorized
|
- Authorizes anyone, but must be authorized
|
||||||
- Side-effect-free: returns `{ pubkey, is_admin }` only
|
- If a tenant for the identity doesn't exist:
|
||||||
- Clients must call `POST /tenants` before any tenant-scoped write
|
- Call the Stripe API to create a new customer
|
||||||
|
- Create a new tenant using `command.create_tenant` with payload and `stripe_customer_id`. No subscription is created yet — that happens when the first relay is added.
|
||||||
- Return `data` is an `Identity` struct
|
- Return `data` is an `Identity` struct
|
||||||
|
|
||||||
--- Tenant routes
|
--- Tenant routes
|
||||||
@@ -58,18 +59,6 @@ Notes:
|
|||||||
- Authorizes admin only
|
- Authorizes admin only
|
||||||
- Return `data` is a list of tenant structs from `query.list_tenants`
|
- Return `data` is a list of tenant structs from `query.list_tenants`
|
||||||
|
|
||||||
## `async fn create_tenant(...) -> Response`
|
|
||||||
|
|
||||||
- Serves `POST /tenants`
|
|
||||||
- Authorizes anyone, but must be authorized
|
|
||||||
- No request body; target pubkey is derived from NIP-98 auth
|
|
||||||
- Idempotent: if a tenant already exists for the auth pubkey, return it without calling Stripe or writing to the DB
|
|
||||||
- Otherwise, call the Stripe API to create a new customer and create a new tenant using `command.create_tenant` with the resulting `stripe_customer_id`. No subscription is created yet — that happens when the first relay is added.
|
|
||||||
- On unique-constraint race (`pubkey-exists`), re-fetch and return the existing tenant
|
|
||||||
- If Stripe customer creation fails, return `code=stripe-customer-create-failed`
|
|
||||||
- Always returns `200` (create-or-get is uniform)
|
|
||||||
- Return `data` is a single `Tenant` struct
|
|
||||||
|
|
||||||
## `async fn get_tenant(...) -> Response`
|
## `async fn get_tenant(...) -> Response`
|
||||||
|
|
||||||
- Serves `GET /tenants/:pubkey`
|
- Serves `GET /tenants/:pubkey`
|
||||||
@@ -132,7 +121,7 @@ Notes:
|
|||||||
|
|
||||||
- Serves `POST /relays/:id/deactivate`
|
- Serves `POST /relays/:id/deactivate`
|
||||||
- Authorizes admin or relay owner
|
- Authorizes admin or relay owner
|
||||||
- If relay status is `inactive` or `delinquent`, return a `400` with `code=relay-is-inactive`
|
- If relay is already inactive, return a `400` with `code=relay-is-inactive`
|
||||||
- Call `command.deactivate_relay`
|
- Call `command.deactivate_relay`
|
||||||
- Return `data` is empty
|
- Return `data` is empty
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ Manages the Stripe subscription and subscription items for a relay's tenant. Onl
|
|||||||
|
|
||||||
- Fetch the relay and tenant associated with the `activity`
|
- Fetch the relay and tenant associated with the `activity`
|
||||||
- **If relay plan is `free`**: if the relay has a `stripe_subscription_item_id`, delete it via the Stripe API and call `command.delete_relay_subscription_item`. Then check cleanup (below). Return early.
|
- **If relay plan is `free`**: if the relay has a `stripe_subscription_item_id`, delete it via the Stripe API and call `command.delete_relay_subscription_item`. Then check cleanup (below). Return early.
|
||||||
- **If relay is `inactive` or `delinquent`**: if the relay has a `stripe_subscription_item_id`, delete it via the Stripe API and call `command.delete_relay_subscription_item`. Then check cleanup (below). Return early.
|
- **If relay is `inactive`**: if the relay has a `stripe_subscription_item_id`, delete it via the Stripe API and call `command.delete_relay_subscription_item`. Then check cleanup (below). Return early.
|
||||||
- **If relay is `active` and on a paid plan**:
|
- **If relay is `active` and on a paid plan**:
|
||||||
- **Ensure subscription exists**: If the tenant has no `stripe_subscription_id`, create a Stripe subscription for the customer with `collection_method: "charge_automatically"` and the relay's price as the first item. Save the subscription ID via `command.set_tenant_subscription` and the item ID via `command.set_relay_subscription_item`. Return early.
|
- **Ensure subscription exists**: If the tenant has no `stripe_subscription_id`, create a Stripe subscription for the customer with `collection_method: "charge_automatically"` and the relay's price as the first item. Save the subscription ID via `command.set_tenant_subscription` and the item ID via `command.set_relay_subscription_item`. Return early.
|
||||||
- **Sync the subscription item**: If the tenant already has a subscription, create or update the relay's Stripe subscription item to the plan's `stripe_price_id` via the Stripe API, then call `command.set_relay_subscription_item`.
|
- **Sync the subscription item**: If the tenant already has a subscription, create or update the relay's Stripe subscription item to the plan's `stripe_price_id` via the Stripe API, then call `command.set_relay_subscription_item`.
|
||||||
@@ -85,7 +85,7 @@ Skip invoices with `amount_due` of 0.
|
|||||||
- Look up tenant by `stripe_customer_id`
|
- Look up tenant by `stripe_customer_id`
|
||||||
- If tenant has `past_due_at` set:
|
- If tenant has `past_due_at` set:
|
||||||
- Clear `past_due_at` via `command.clear_tenant_past_due`
|
- Clear `past_due_at` via `command.clear_tenant_past_due`
|
||||||
- Find all `delinquent` relays on paid plans for the tenant (relays marked delinquent by the billing system due to non-payment)
|
- Find all `inactive` relays for the tenant that were deactivated due to non-payment (i.e. relays on paid plans that are inactive)
|
||||||
- Reactivate each one via `command.activate_relay`
|
- Reactivate each one via `command.activate_relay`
|
||||||
|
|
||||||
## `fn handle_invoice_payment_failed(&self, invoice: &Invoice)`
|
## `fn handle_invoice_payment_failed(&self, invoice: &Invoice)`
|
||||||
@@ -98,7 +98,7 @@ Skip invoices with `amount_due` of 0.
|
|||||||
## `fn handle_invoice_overdue(&self, invoice: &Invoice)`
|
## `fn handle_invoice_overdue(&self, invoice: &Invoice)`
|
||||||
|
|
||||||
- Look up tenant by `stripe_customer_id`
|
- Look up tenant by `stripe_customer_id`
|
||||||
- Mark all active relays on paid plans as delinquent via `command.mark_relay_delinquent` (sets status to `delinquent`, distinct from user-initiated `deactivate_relay`)
|
- Deactivate all active relays on paid plans via `command.deactivate_relay`
|
||||||
- Send a DM via `robot.send_dm` notifying the tenant that their paid relays have been deactivated due to non-payment
|
- Send a DM via `robot.send_dm` notifying the tenant that their paid relays have been deactivated due to non-payment
|
||||||
|
|
||||||
## `fn handle_subscription_updated(&self, subscription: &Subscription)`
|
## `fn handle_subscription_updated(&self, subscription: &Subscription)`
|
||||||
@@ -106,7 +106,7 @@ Skip invoices with `amount_due` of 0.
|
|||||||
- Look up tenant by `stripe_customer_id`
|
- Look up tenant by `stripe_customer_id`
|
||||||
- If subscription status is `canceled` or `unpaid`:
|
- If subscription status is `canceled` or `unpaid`:
|
||||||
- Clear `stripe_subscription_id` via `command.clear_tenant_subscription`
|
- Clear `stripe_subscription_id` via `command.clear_tenant_subscription`
|
||||||
- Mark all active paid relays as delinquent via `command.mark_relay_delinquent`
|
- Deactivate all active paid relays for the tenant via `command.deactivate_relay`
|
||||||
|
|
||||||
## `fn handle_subscription_deleted(&self, subscription: &Subscription)`
|
## `fn handle_subscription_deleted(&self, subscription: &Subscription)`
|
||||||
|
|
||||||
|
|||||||
@@ -44,14 +44,6 @@ Notes:
|
|||||||
|
|
||||||
- Sets relay status to `inactive`
|
- Sets relay status to `inactive`
|
||||||
- Logs activity as `(deactivate_relay, relay_id)`
|
- Logs activity as `(deactivate_relay, relay_id)`
|
||||||
- Used for user/admin-initiated deactivation only
|
|
||||||
|
|
||||||
## `pub fn mark_relay_delinquent(&self, relay: &Relay) -> Result<()>`
|
|
||||||
|
|
||||||
- Sets relay status to `delinquent`
|
|
||||||
- Logs activity as `(deactivate_relay, relay_id)`
|
|
||||||
- Used exclusively by the billing system when a relay's subscription becomes past due
|
|
||||||
- `delinquent` relays are automatically reactivated via `activate_relay` when payment is received
|
|
||||||
|
|
||||||
## `pub fn activate_relay(&self, relay: &Relay) -> Result<()>`
|
## `pub fn activate_relay(&self, relay: &Relay) -> Result<()>`
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,5 @@ Members:
|
|||||||
## `async fn sync_relay(&self, relay: &Relay, is_new: bool)`
|
## `async fn sync_relay(&self, relay: &Relay, is_new: bool)`
|
||||||
|
|
||||||
- If `is_new`, sends `POST /relay/:id` to create the relay in zooid.
|
- If `is_new`, sends `POST /relay/:id` to create the relay in zooid.
|
||||||
- Otherwise, sends `PATCH /relay/:id` to update it.
|
- Otherwise, sends `PUT /relay/:id` to update it.
|
||||||
- Includes `secret` only for relay creation (`POST`) so updates do not rotate relay identity.
|
- Passes full relay configuration in the body including host, schema, secret, inactive flag, info, policy, groups, management, blossom, livekit, push, and roles.
|
||||||
- Passes relay configuration in the body including host, schema, inactive flag, info, policy, groups, management, blossom, livekit, push, and roles.
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ A relay is a nostr relay owned by a `tenant` and hosted by the attached zooid in
|
|||||||
- `subdomain` - the relay's subdomain
|
- `subdomain` - the relay's subdomain
|
||||||
- `plan` - the relay's plan
|
- `plan` - the relay's plan
|
||||||
- `stripe_subscription_item_id` (nullable) - the Stripe subscription item id. Only set for relays on paid plans.
|
- `stripe_subscription_item_id` (nullable) - the Stripe subscription item id. Only set for relays on paid plans.
|
||||||
- `status` - one of `active|inactive|delinquent`. Only `active` relays count toward billing. `delinquent` is set by the billing system when a relay's subscription becomes past due; `inactive` is set when a user or admin manually deactivates a relay.
|
- `status` - `active|inactive`. Only `active` relays count toward billing.
|
||||||
- `synced` - whether the relay has been successfully synced to zooid at least once.
|
- `synced` - whether the relay has been successfully synced to zooid at least once.
|
||||||
- `sync_error` - a string indicating any errors encountered when synchronizing.
|
- `sync_error` - a string indicating any errors encountered when synchronizing.
|
||||||
- `info_name` - the relay's name
|
- `info_name` - the relay's name
|
||||||
@@ -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:
|
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 relay's `host` is calculated based on `subdomain` + `RELAY_DOMAIN`
|
||||||
- The value of `inactive` is calculated based on `status`
|
- 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 `livekit_*` configuration is inferred based on environment variables and `livekit_enabled`.
|
||||||
- The relay's `roles` are hard-coded for now.
|
- The relay's `roles` are hard-coded for now.
|
||||||
|
|
||||||
|
|||||||
+21
-36
@@ -143,7 +143,7 @@ impl Api {
|
|||||||
.route("/identity", get(get_identity))
|
.route("/identity", get(get_identity))
|
||||||
.route("/plans", get(list_plans))
|
.route("/plans", get(list_plans))
|
||||||
.route("/plans/:id", get(get_plan))
|
.route("/plans/:id", get(get_plan))
|
||||||
.route("/tenants", get(list_tenants).post(create_tenant))
|
.route("/tenants", get(list_tenants))
|
||||||
.route("/tenants/:pubkey", get(get_tenant).put(update_tenant))
|
.route("/tenants/:pubkey", get(get_tenant).put(update_tenant))
|
||||||
.route("/tenants/:pubkey/relays", get(list_tenant_relays))
|
.route("/tenants/:pubkey/relays", get(list_tenant_relays))
|
||||||
.route("/relays", get(list_relays).post(create_relay))
|
.route("/relays", get(list_relays).post(create_relay))
|
||||||
@@ -401,17 +401,10 @@ async fn get_identity(
|
|||||||
) -> std::result::Result<Response, ApiError> {
|
) -> std::result::Result<Response, ApiError> {
|
||||||
let pubkey = state.api.extract_auth_pubkey(&headers)?;
|
let pubkey = state.api.extract_auth_pubkey(&headers)?;
|
||||||
let is_admin = state.api.admins.iter().any(|a| a == &pubkey);
|
let is_admin = state.api.admins.iter().any(|a| a == &pubkey);
|
||||||
Ok(ok(StatusCode::OK, IdentityResponse { pubkey, is_admin }))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn create_tenant(
|
|
||||||
State(state): State<AppState>,
|
|
||||||
headers: HeaderMap,
|
|
||||||
) -> std::result::Result<Response, ApiError> {
|
|
||||||
let pubkey = state.api.extract_auth_pubkey(&headers)?;
|
|
||||||
|
|
||||||
|
// Ensure tenant exists.
|
||||||
match state.api.query.get_tenant(&pubkey).await {
|
match state.api.query.get_tenant(&pubkey).await {
|
||||||
Ok(Some(t)) => Ok(ok(StatusCode::OK, t)),
|
Ok(Some(_)) => {}
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
let stripe_customer_id = match state.api.billing.stripe_create_customer(&pubkey).await {
|
let stripe_customer_id = match state.api.billing.stripe_create_customer(&pubkey).await {
|
||||||
Ok(id) => id,
|
Ok(id) => id,
|
||||||
@@ -435,35 +428,27 @@ async fn create_tenant(
|
|||||||
};
|
};
|
||||||
|
|
||||||
match state.api.command.create_tenant(&tenant).await {
|
match state.api.command.create_tenant(&tenant).await {
|
||||||
Ok(()) => Ok(ok(StatusCode::OK, tenant)),
|
Ok(()) => {}
|
||||||
Err(e) if matches!(map_unique_error(&e), Some("pubkey-exists")) => {
|
Err(e) if matches!(map_unique_error(&e), Some("pubkey-exists")) => {}
|
||||||
match state.api.query.get_tenant(&pubkey).await {
|
Err(e) => {
|
||||||
Ok(Some(t)) => Ok(ok(StatusCode::OK, t)),
|
return Ok(err(
|
||||||
Ok(None) => Ok(err(
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
"internal",
|
||||||
"internal",
|
&e.to_string(),
|
||||||
"tenant row missing after unique-constraint race",
|
));
|
||||||
)),
|
|
||||||
Err(e) => Ok(err(
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
"internal",
|
|
||||||
&e.to_string(),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Err(e) => Ok(err(
|
};
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
}
|
||||||
"internal",
|
Err(e) => {
|
||||||
&e.to_string(),
|
return Ok(err(
|
||||||
)),
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
}
|
"internal",
|
||||||
|
&e.to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
Err(e) => Ok(err(
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
"internal",
|
|
||||||
&e.to_string(),
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(ok(StatusCode::OK, IdentityResponse { pubkey, is_admin }))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_plan(Path(id): Path<String>) -> Response {
|
async fn get_plan(Path(id): Path<String>) -> Response {
|
||||||
|
|||||||
+48
-68
@@ -2,7 +2,7 @@ use anyhow::Result;
|
|||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
|
|
||||||
use crate::command::Command;
|
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;
|
use crate::query::Query;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -70,7 +70,7 @@ impl Infra {
|
|||||||
Ok(())
|
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 {
|
match self.sync_relay(relay, is_new).await {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
tracing::info!(relay = %relay.id, "relay sync succeeded");
|
tracing::info!(relay = %relay.id, "relay sync succeeded");
|
||||||
@@ -96,7 +96,7 @@ impl Infra {
|
|||||||
Ok(auth)
|
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 client = reqwest::Client::new();
|
||||||
let base = self.api_url.trim_end_matches('/');
|
let base = self.api_url.trim_end_matches('/');
|
||||||
|
|
||||||
@@ -106,6 +106,8 @@ impl Infra {
|
|||||||
format!("{}.{}", relay.subdomain, self.relay_domain)
|
format!("{}.{}", relay.subdomain, self.relay_domain)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let secret = Keys::generate().secret_key().to_secret_hex();
|
||||||
|
|
||||||
let livekit = if relay.livekit_enabled == 1 {
|
let livekit = if relay.livekit_enabled == 1 {
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
@@ -117,28 +119,53 @@ impl Infra {
|
|||||||
serde_json::json!({ "enabled": false })
|
serde_json::json!({ "enabled": false })
|
||||||
};
|
};
|
||||||
|
|
||||||
let body = relay_sync_body(
|
let body = serde_json::json!({
|
||||||
relay,
|
"host": host,
|
||||||
host,
|
"schema": relay.schema,
|
||||||
livekit,
|
"secret": secret,
|
||||||
is_new.then(|| Keys::generate().secret_key().to_secret_hex()),
|
"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 response = if is_new {
|
||||||
let auth = self.nip98_auth(&url, zooid_sync_http_method(is_new)).await?;
|
let url = format!("{}/relay/{}", base, relay.id);
|
||||||
|
let auth = self.nip98_auth(&url, HttpMethod::POST).await?;
|
||||||
let request = if is_new {
|
client
|
||||||
client.post(&url)
|
.post(&url)
|
||||||
|
.header("Authorization", auth)
|
||||||
|
.json(&body)
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
} else {
|
} 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() {
|
if !response.status().is_success() {
|
||||||
let status = response.status();
|
let status = response.status();
|
||||||
let body = response.text().await.unwrap_or_default();
|
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 {
|
fn should_sync_relay_activity(activity_type: &str) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
activity_type,
|
activity_type,
|
||||||
|
|||||||
@@ -203,13 +203,6 @@ export default function RelayDetailCard(props: RelayDetailCardProps) {
|
|||||||
</Show>
|
</Show>
|
||||||
</div>
|
</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" />
|
<hr class="border-gray-200" />
|
||||||
|
|
||||||
<DetailSection title="Policy">
|
<DetailSection title="Policy">
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { A } from "@solidjs/router"
|
import { A } from "@solidjs/router"
|
||||||
import { Show } from "solid-js"
|
|
||||||
import type { Relay } from "@/lib/api"
|
import type { Relay } from "@/lib/api"
|
||||||
|
|
||||||
type RelayListItemProps = {
|
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>
|
<p class="text-xs text-gray-500 break-all mt-1">Tenant: {props.relay.tenant}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Show
|
<p class="text-xs uppercase tracking-wide text-gray-500">{props.relay.status}</p>
|
||||||
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>
|
|
||||||
</div>
|
</div>
|
||||||
</A>
|
</A>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -205,10 +205,6 @@ export function getIdentity() {
|
|||||||
return callApi<undefined, Identity>("GET", "/identity")
|
return callApi<undefined, Identity>("GET", "/identity")
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createTenant() {
|
|
||||||
return callApi<undefined, Tenant>("POST", "/tenants")
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getPlan(id: string) {
|
export function getPlan(id: string) {
|
||||||
return callApi<undefined, Plan>("GET", `/plans/${id}`)
|
return callApi<undefined, Plan>("GET", `/plans/${id}`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { PasswordSigner } from "applesauce-signers"
|
|||||||
import QrScanner from "qr-scanner"
|
import QrScanner from "qr-scanner"
|
||||||
import QRCode from "qrcode"
|
import QRCode from "qrcode"
|
||||||
import { accountManager, identity, PLATFORM_NAME } from "@/lib/state"
|
import { accountManager, identity, PLATFORM_NAME } from "@/lib/state"
|
||||||
import { createTenant } from "@/lib/api"
|
|
||||||
import useMinLoading from "@/components/useMinLoading"
|
import useMinLoading from "@/components/useMinLoading"
|
||||||
|
|
||||||
const NIP46_RELAYS = ['wss://bucket.coracle.social', 'wss://ephemeral.snowflare.cc']
|
const NIP46_RELAYS = ['wss://bucket.coracle.social', 'wss://ephemeral.snowflare.cc']
|
||||||
@@ -70,12 +69,6 @@ export default function Login(props: LoginPageProps = {}) {
|
|||||||
async function completeLogin(account: ExtensionAccount | NostrConnectAccount | PrivateKeyAccount | PasswordAccount) {
|
async function completeLogin(account: ExtensionAccount | NostrConnectAccount | PrivateKeyAccount | PasswordAccount) {
|
||||||
accountManager.addAccount(account)
|
accountManager.addAccount(account)
|
||||||
accountManager.setActive(account)
|
accountManager.setActive(account)
|
||||||
try {
|
|
||||||
await createTenant()
|
|
||||||
} catch (e) {
|
|
||||||
accountManager.removeAccount(account)
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
await props.onAuthenticated?.()
|
await props.onAuthenticated?.()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user