forked from coracle/caravel
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 086c8f080d | |||
| ca26d41eef | |||
| 44f9928070 |
+1
-1
@@ -121,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 is already inactive, return a `400` with `code=relay-is-inactive`
|
- If relay status is `inactive` or `delinquent`, 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`**: 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 `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 `inactive` relays for the tenant that were deactivated due to non-payment (i.e. relays on paid plans that are inactive)
|
- Find all `delinquent` relays on paid plans for the tenant (relays marked delinquent by the billing system due to non-payment)
|
||||||
- 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`
|
||||||
- Deactivate all active relays on paid plans via `command.deactivate_relay`
|
- Mark all active relays on paid plans as delinquent via `command.mark_relay_delinquent` (sets status to `delinquent`, distinct from user-initiated `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`
|
||||||
- Deactivate all active paid relays for the tenant via `command.deactivate_relay`
|
- Mark all active paid relays as delinquent via `command.mark_relay_delinquent`
|
||||||
|
|
||||||
## `fn handle_subscription_deleted(&self, subscription: &Subscription)`
|
## `fn handle_subscription_deleted(&self, subscription: &Subscription)`
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,14 @@ 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,5 +30,6 @@ 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 `PUT /relay/:id` to update it.
|
- Otherwise, sends `PATCH /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.
|
- 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.
|
||||||
|
|||||||
@@ -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` - `active|inactive`. Only `active` relays count toward billing.
|
- `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.
|
||||||
- `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,9 +85,8 @@ 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 and persisted to the zooid configuration but isn't stored in the database.
|
- 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 `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.
|
||||||
|
|
||||||
|
|||||||
+68
-48
@@ -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_STATUS_DELINQUENT, RELAY_STATUS_INACTIVE};
|
use crate::models::{Activity, Relay, 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: &crate::models::Relay, is_new: bool) {
|
async fn sync_and_report(&self, relay: &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: &crate::models::Relay, is_new: bool) -> Result<()> {
|
async fn sync_relay(&self, relay: &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,8 +106,6 @@ 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,
|
||||||
@@ -119,53 +117,28 @@ impl Infra {
|
|||||||
serde_json::json!({ "enabled": false })
|
serde_json::json!({ "enabled": false })
|
||||||
};
|
};
|
||||||
|
|
||||||
let body = serde_json::json!({
|
let body = relay_sync_body(
|
||||||
"host": host,
|
relay,
|
||||||
"schema": relay.schema,
|
host,
|
||||||
"secret": secret,
|
livekit,
|
||||||
"inactive": relay.status == RELAY_STATUS_INACTIVE
|
is_new.then(|| Keys::generate().secret_key().to_secret_hex()),
|
||||||
|| 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 response = if is_new {
|
let url = format!("{}/relay/{}", base, relay.id);
|
||||||
let url = format!("{}/relay/{}", base, relay.id);
|
let auth = self.nip98_auth(&url, zooid_sync_http_method(is_new)).await?;
|
||||||
let auth = self.nip98_auth(&url, HttpMethod::POST).await?;
|
|
||||||
client
|
let request = if is_new {
|
||||||
.post(&url)
|
client.post(&url)
|
||||||
.header("Authorization", auth)
|
|
||||||
.json(&body)
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
} else {
|
} else {
|
||||||
let url = format!("{}/relay/{}", base, relay.id);
|
client.patch(&url)
|
||||||
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();
|
||||||
@@ -175,6 +148,53 @@ 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,
|
||||||
|
|||||||
Reference in New Issue
Block a user