forked from coracle/caravel
Add provisioner
This commit is contained in:
@@ -16,3 +16,6 @@ nostr-sdk = "0.39"
|
|||||||
uuid = { version = "1.7", features = ["v4"] }
|
uuid = { version = "1.7", features = ["v4"] }
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
tower-http = { version = "0.5", features = ["cors"] }
|
tower-http = { version = "0.5", features = ["cors"] }
|
||||||
|
reqwest = { version = "0.12", features = ["json", "rustls-tls"] }
|
||||||
|
rand = "0.8"
|
||||||
|
hex = "0.4"
|
||||||
|
|||||||
+3
-1
@@ -32,6 +32,9 @@ Environment variables:
|
|||||||
| `DATABASE_URL` | SQLite database URL | `sqlite://data/hosting.db` |
|
| `DATABASE_URL` | SQLite database URL | `sqlite://data/hosting.db` |
|
||||||
| `HOST` | Bind host | `127.0.0.1` |
|
| `HOST` | Bind host | `127.0.0.1` |
|
||||||
| `PORT` | Bind port | `3000` |
|
| `PORT` | Bind port | `3000` |
|
||||||
|
| `ZOOID_API_URL` | Zooid API base URL | `http://127.0.0.1:8032` |
|
||||||
|
| `PLATFORM_SECRET` | Platform Nostr secret key for NIP-98 auth | _required_ |
|
||||||
|
| `RELAY_DOMAIN` | Relay base domain for subdomains | `spaces.coracle.social` |
|
||||||
|
|
||||||
The database directory is created automatically if it doesn’t exist.
|
The database directory is created automatically if it doesn’t exist.
|
||||||
|
|
||||||
@@ -92,5 +95,4 @@ Admin routes (all require NIP-98 auth; pubkey must be in `HOSTING_ADMIN_PUBKEYS`
|
|||||||
|
|
||||||
## Next Steps
|
## Next Steps
|
||||||
|
|
||||||
- Add relay provisioning worker (zooid API calls)
|
|
||||||
- Add invoice generation and billing jobs
|
- Add invoice generation and billing jobs
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE relays ADD COLUMN icon TEXT NOT NULL DEFAULT "";
|
||||||
+25
-2
@@ -13,12 +13,14 @@ use uuid::Uuid;
|
|||||||
|
|
||||||
use crate::auth::verify_nip98;
|
use crate::auth::verify_nip98;
|
||||||
use crate::models::{NewRelay, NewTenant, Relay, UpdateRelay};
|
use crate::models::{NewRelay, NewTenant, Relay, UpdateRelay};
|
||||||
|
use crate::provisioning::Provisioner;
|
||||||
use crate::repo::Repo;
|
use crate::repo::Repo;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub repo: Repo,
|
pub repo: Repo,
|
||||||
pub admin_pubkeys: Arc<Vec<String>>,
|
pub admin_pubkeys: Arc<Vec<String>>,
|
||||||
|
pub provisioner: Provisioner,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn router(state: AppState) -> Router {
|
pub fn router(state: AppState) -> Router {
|
||||||
@@ -145,6 +147,7 @@ async fn list_tenant_relays(
|
|||||||
struct CreateRelayRequest {
|
struct CreateRelayRequest {
|
||||||
name: String,
|
name: String,
|
||||||
subdomain: String,
|
subdomain: String,
|
||||||
|
icon: String,
|
||||||
description: String,
|
description: String,
|
||||||
plan: String,
|
plan: String,
|
||||||
}
|
}
|
||||||
@@ -177,6 +180,7 @@ async fn create_tenant_relay(
|
|||||||
name: payload.name,
|
name: payload.name,
|
||||||
subdomain: payload.subdomain.clone(),
|
subdomain: payload.subdomain.clone(),
|
||||||
schema: payload.subdomain.replace('-', "_"),
|
schema: payload.subdomain.replace('-', "_"),
|
||||||
|
icon: payload.icon,
|
||||||
description: payload.description,
|
description: payload.description,
|
||||||
plan: payload.plan,
|
plan: payload.plan,
|
||||||
status: "pending".to_string(),
|
status: "pending".to_string(),
|
||||||
@@ -186,7 +190,7 @@ async fn create_tenant_relay(
|
|||||||
return (StatusCode::INTERNAL_SERVER_ERROR, Json(ApiError { error: "failed to create relay".into() })).into_response();
|
return (StatusCode::INTERNAL_SERVER_ERROR, Json(ApiError { error: "failed to create relay".into() })).into_response();
|
||||||
}
|
}
|
||||||
|
|
||||||
spawn_provisioning_worker(relay.clone());
|
spawn_provisioning_worker(state.repo.clone(), state.provisioner.clone(), relay.clone());
|
||||||
|
|
||||||
(StatusCode::CREATED, Json(relay)).into_response()
|
(StatusCode::CREATED, Json(relay)).into_response()
|
||||||
}
|
}
|
||||||
@@ -215,6 +219,7 @@ async fn get_tenant_relay(
|
|||||||
struct UpdateRelayRequest {
|
struct UpdateRelayRequest {
|
||||||
name: String,
|
name: String,
|
||||||
subdomain: String,
|
subdomain: String,
|
||||||
|
icon: String,
|
||||||
description: String,
|
description: String,
|
||||||
plan: String,
|
plan: String,
|
||||||
}
|
}
|
||||||
@@ -247,6 +252,7 @@ async fn update_tenant_relay(
|
|||||||
name: payload.name,
|
name: payload.name,
|
||||||
subdomain: payload.subdomain.clone(),
|
subdomain: payload.subdomain.clone(),
|
||||||
schema: payload.subdomain.replace('-', "_"),
|
schema: payload.subdomain.replace('-', "_"),
|
||||||
|
icon: payload.icon,
|
||||||
description: payload.description,
|
description: payload.description,
|
||||||
plan: payload.plan,
|
plan: payload.plan,
|
||||||
status: existing.status,
|
status: existing.status,
|
||||||
@@ -286,6 +292,7 @@ async fn deactivate_tenant_relay(
|
|||||||
name: existing.name,
|
name: existing.name,
|
||||||
subdomain: existing.subdomain.clone(),
|
subdomain: existing.subdomain.clone(),
|
||||||
schema: existing.subdomain.replace('-', "_"),
|
schema: existing.subdomain.replace('-', "_"),
|
||||||
|
icon: existing.icon,
|
||||||
description: existing.description,
|
description: existing.description,
|
||||||
plan: existing.plan,
|
plan: existing.plan,
|
||||||
status: "deactivated".to_string(),
|
status: "deactivated".to_string(),
|
||||||
@@ -489,6 +496,7 @@ async fn admin_update_relay(
|
|||||||
name: payload.name,
|
name: payload.name,
|
||||||
subdomain: payload.subdomain.clone(),
|
subdomain: payload.subdomain.clone(),
|
||||||
schema: payload.subdomain.replace('-', "_"),
|
schema: payload.subdomain.replace('-', "_"),
|
||||||
|
icon: payload.icon,
|
||||||
description: payload.description,
|
description: payload.description,
|
||||||
plan: payload.plan,
|
plan: payload.plan,
|
||||||
status: existing.status,
|
status: existing.status,
|
||||||
@@ -528,6 +536,7 @@ async fn admin_deactivate_relay(
|
|||||||
name: existing.name,
|
name: existing.name,
|
||||||
subdomain: existing.subdomain.clone(),
|
subdomain: existing.subdomain.clone(),
|
||||||
schema: existing.subdomain.replace('-', "_"),
|
schema: existing.subdomain.replace('-', "_"),
|
||||||
|
icon: existing.icon,
|
||||||
description: existing.description,
|
description: existing.description,
|
||||||
plan: existing.plan,
|
plan: existing.plan,
|
||||||
status: "deactivated".to_string(),
|
status: "deactivated".to_string(),
|
||||||
@@ -540,8 +549,22 @@ async fn admin_deactivate_relay(
|
|||||||
(StatusCode::OK, Json(updated)).into_response()
|
(StatusCode::OK, Json(updated)).into_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_provisioning_worker(relay: NewRelay) {
|
fn spawn_provisioning_worker(repo: Repo, provisioner: Provisioner, relay: NewRelay) {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
tracing::info!(relay_id = relay.id, "provisioning worker started");
|
tracing::info!(relay_id = relay.id, "provisioning worker started");
|
||||||
|
if let Err(err) = provisioner.provision_relay(&relay).await {
|
||||||
|
tracing::error!(relay_id = relay.id, error = %err, "provisioning failed");
|
||||||
|
if let Err(err) = repo
|
||||||
|
.update_relay_status(&relay.id, "provisioning_failed")
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
tracing::error!(relay_id = relay.id, error = %err, "failed to update relay status");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(err) = repo.update_relay_status(&relay.id, "active").await {
|
||||||
|
tracing::error!(relay_id = relay.id, error = %err, "failed to update relay status");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -11,5 +11,5 @@ pub fn verify_nip98(auth_header: &str, url: &str, method: &str) -> Result<Public
|
|||||||
let method = HttpMethod::from_str(&method.to_uppercase()).map_err(|e| anyhow!(e))?;
|
let method = HttpMethod::from_str(&method.to_uppercase()).map_err(|e| anyhow!(e))?;
|
||||||
let now = Timestamp::now();
|
let now = Timestamp::now();
|
||||||
|
|
||||||
nip98::verify_auth_header(auth_header, &url, method, now).map_err(|e| anyhow!(e))
|
nip98::verify_auth_header(auth_header, &url, method, now, None).map_err(|e| anyhow!(e))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ pub struct Config {
|
|||||||
pub host: String,
|
pub host: String,
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
pub admin_pubkeys: Vec<String>,
|
pub admin_pubkeys: Vec<String>,
|
||||||
|
pub zooid_api_url: String,
|
||||||
|
pub platform_secret: String,
|
||||||
|
pub relay_domain: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
@@ -23,12 +26,20 @@ impl Config {
|
|||||||
.map(|v| v.trim().to_string())
|
.map(|v| v.trim().to_string())
|
||||||
.filter(|v| !v.is_empty())
|
.filter(|v| !v.is_empty())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
let zooid_api_url =
|
||||||
|
env::var("ZOOID_API_URL").unwrap_or_else(|_| "http://127.0.0.1:8032".to_string());
|
||||||
|
let platform_secret = env::var("PLATFORM_SECRET").unwrap_or_default();
|
||||||
|
let relay_domain =
|
||||||
|
env::var("RELAY_DOMAIN").unwrap_or_else(|_| "spaces.coracle.social".to_string());
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
database_url,
|
database_url,
|
||||||
host,
|
host,
|
||||||
port,
|
port,
|
||||||
admin_pubkeys,
|
admin_pubkeys,
|
||||||
|
zooid_api_url,
|
||||||
|
platform_secret,
|
||||||
|
relay_domain,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ mod auth;
|
|||||||
mod config;
|
mod config;
|
||||||
mod db;
|
mod db;
|
||||||
mod models;
|
mod models;
|
||||||
|
mod provisioning;
|
||||||
mod repo;
|
mod repo;
|
||||||
|
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
@@ -15,6 +16,7 @@ use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
|||||||
|
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::db::init_pool;
|
use crate::db::init_pool;
|
||||||
|
use crate::provisioning::Provisioner;
|
||||||
use crate::repo::Repo;
|
use crate::repo::Repo;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@@ -29,9 +31,15 @@ async fn main() -> Result<()> {
|
|||||||
|
|
||||||
let pool = init_pool(&config.database_url).await?;
|
let pool = init_pool(&config.database_url).await?;
|
||||||
let repo = Repo::new(pool);
|
let repo = Repo::new(pool);
|
||||||
|
let provisioner = Provisioner::new(
|
||||||
|
config.zooid_api_url.clone(),
|
||||||
|
config.relay_domain.clone(),
|
||||||
|
config.platform_secret.clone(),
|
||||||
|
)?;
|
||||||
let state = api::AppState {
|
let state = api::AppState {
|
||||||
repo,
|
repo,
|
||||||
admin_pubkeys: std::sync::Arc::new(config.admin_pubkeys.clone()),
|
admin_pubkeys: std::sync::Arc::new(config.admin_pubkeys.clone()),
|
||||||
|
provisioner,
|
||||||
};
|
};
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ pub struct Relay {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
pub subdomain: String,
|
pub subdomain: String,
|
||||||
pub schema: String,
|
pub schema: String,
|
||||||
|
pub icon: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub plan: String,
|
pub plan: String,
|
||||||
pub status: String,
|
pub status: String,
|
||||||
@@ -31,6 +32,7 @@ pub struct NewRelay {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
pub subdomain: String,
|
pub subdomain: String,
|
||||||
pub schema: String,
|
pub schema: String,
|
||||||
|
pub icon: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub plan: String,
|
pub plan: String,
|
||||||
pub status: String,
|
pub status: String,
|
||||||
@@ -42,6 +44,7 @@ pub struct UpdateRelay {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
pub subdomain: String,
|
pub subdomain: String,
|
||||||
pub schema: String,
|
pub schema: String,
|
||||||
|
pub icon: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub plan: String,
|
pub plan: String,
|
||||||
pub status: String,
|
pub status: String,
|
||||||
|
|||||||
@@ -0,0 +1,178 @@
|
|||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
use rand::RngCore;
|
||||||
|
use reqwest::Client;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use nostr_sdk::nostr::nips::nip98::{HttpData, HttpMethod};
|
||||||
|
use nostr_sdk::nostr::types::url::Url;
|
||||||
|
use nostr_sdk::nostr::Keys;
|
||||||
|
|
||||||
|
use crate::models::NewRelay;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Provisioner {
|
||||||
|
base_url: String,
|
||||||
|
relay_domain: String,
|
||||||
|
admin_keys: Keys,
|
||||||
|
client: Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Provisioner {
|
||||||
|
pub fn new(base_url: String, relay_domain: String, admin_secret: String) -> Result<Self> {
|
||||||
|
if admin_secret.trim().is_empty() {
|
||||||
|
return Err(anyhow!("PLATFORM_SECRET is required"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let admin_keys = Keys::parse(admin_secret)?;
|
||||||
|
let client = Client::new();
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
base_url,
|
||||||
|
relay_domain,
|
||||||
|
admin_keys,
|
||||||
|
client,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn provision_relay(&self, relay: &NewRelay) -> Result<()> {
|
||||||
|
let host = format!("{}.{}", relay.subdomain, self.relay_domain);
|
||||||
|
let secret = generate_secret_hex();
|
||||||
|
|
||||||
|
let payload = ZooidConfig::new(relay, host, secret);
|
||||||
|
let url = format!("{}/relay/{}", self.base_url.trim_end_matches('/'), relay.id);
|
||||||
|
let auth = self.build_auth_header(&url, HttpMethod::POST).await?;
|
||||||
|
|
||||||
|
let res = self
|
||||||
|
.client
|
||||||
|
.post(&url)
|
||||||
|
.header(reqwest::header::AUTHORIZATION, auth)
|
||||||
|
.json(&payload)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !res.status().is_success() {
|
||||||
|
let status = res.status();
|
||||||
|
let body = res.text().await.unwrap_or_default();
|
||||||
|
return Err(anyhow!("zooid provisioning failed: {} {}", status, body));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn build_auth_header(&self, url: &str, method: HttpMethod) -> Result<String> {
|
||||||
|
let url = Url::parse(url)?;
|
||||||
|
let data = HttpData::new(url, method);
|
||||||
|
let header = data.to_authorization(&self.admin_keys).await?;
|
||||||
|
Ok(header)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct ZooidConfig {
|
||||||
|
host: String,
|
||||||
|
schema: String,
|
||||||
|
secret: String,
|
||||||
|
info: ZooidInfo,
|
||||||
|
policy: ZooidPolicy,
|
||||||
|
groups: ZooidGroups,
|
||||||
|
push: ZooidPush,
|
||||||
|
management: ZooidManagement,
|
||||||
|
blossom: ZooidBlossom,
|
||||||
|
roles: ZooidRoles,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ZooidConfig {
|
||||||
|
fn new(relay: &NewRelay, host: String, secret: String) -> Self {
|
||||||
|
let blossom_enabled = relay.plan != "free";
|
||||||
|
|
||||||
|
Self {
|
||||||
|
host,
|
||||||
|
schema: relay.schema.clone(),
|
||||||
|
secret,
|
||||||
|
info: ZooidInfo {
|
||||||
|
name: relay.name.clone(),
|
||||||
|
icon: relay.icon.clone(),
|
||||||
|
pubkey: relay.tenant.clone(),
|
||||||
|
description: relay.description.clone(),
|
||||||
|
},
|
||||||
|
policy: ZooidPolicy {
|
||||||
|
public_join: false,
|
||||||
|
strip_signatures: false,
|
||||||
|
},
|
||||||
|
groups: ZooidGroups {
|
||||||
|
enabled: true,
|
||||||
|
auto_join: true,
|
||||||
|
},
|
||||||
|
push: ZooidPush { enabled: true },
|
||||||
|
management: ZooidManagement {
|
||||||
|
enabled: true,
|
||||||
|
methods: Vec::new(),
|
||||||
|
},
|
||||||
|
blossom: ZooidBlossom {
|
||||||
|
enabled: blossom_enabled,
|
||||||
|
},
|
||||||
|
roles: ZooidRoles {
|
||||||
|
member: ZooidRole {
|
||||||
|
pubkeys: Vec::new(),
|
||||||
|
can_invite: true,
|
||||||
|
can_manage: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct ZooidInfo {
|
||||||
|
name: String,
|
||||||
|
icon: String,
|
||||||
|
pubkey: String,
|
||||||
|
description: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct ZooidPolicy {
|
||||||
|
public_join: bool,
|
||||||
|
strip_signatures: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct ZooidGroups {
|
||||||
|
enabled: bool,
|
||||||
|
auto_join: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct ZooidPush {
|
||||||
|
enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct ZooidManagement {
|
||||||
|
enabled: bool,
|
||||||
|
methods: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct ZooidBlossom {
|
||||||
|
enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct ZooidRoles {
|
||||||
|
member: ZooidRole,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct ZooidRole {
|
||||||
|
pubkeys: Vec<String>,
|
||||||
|
can_invite: bool,
|
||||||
|
can_manage: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_secret_hex() -> String {
|
||||||
|
let mut bytes = [0u8; 32];
|
||||||
|
OsRng.fill_bytes(&mut bytes);
|
||||||
|
hex::encode(bytes)
|
||||||
|
}
|
||||||
+17
-6
@@ -67,14 +67,15 @@ impl Repo {
|
|||||||
|
|
||||||
pub async fn create_relay(&self, relay: &NewRelay) -> Result<()> {
|
pub async fn create_relay(&self, relay: &NewRelay) -> Result<()> {
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
"INSERT INTO relays (id, tenant, name, subdomain, schema, description, plan, status)
|
"INSERT INTO relays (id, tenant, name, subdomain, schema, icon, description, plan, status)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
)
|
)
|
||||||
.bind(&relay.id)
|
.bind(&relay.id)
|
||||||
.bind(&relay.tenant)
|
.bind(&relay.tenant)
|
||||||
.bind(&relay.name)
|
.bind(&relay.name)
|
||||||
.bind(&relay.subdomain)
|
.bind(&relay.subdomain)
|
||||||
.bind(&relay.schema)
|
.bind(&relay.schema)
|
||||||
|
.bind(&relay.icon)
|
||||||
.bind(&relay.description)
|
.bind(&relay.description)
|
||||||
.bind(&relay.plan)
|
.bind(&relay.plan)
|
||||||
.bind(&relay.status)
|
.bind(&relay.status)
|
||||||
@@ -85,12 +86,13 @@ impl Repo {
|
|||||||
|
|
||||||
pub async fn update_relay(&self, relay: &UpdateRelay) -> Result<()> {
|
pub async fn update_relay(&self, relay: &UpdateRelay) -> Result<()> {
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
"UPDATE relays SET name = ?, subdomain = ?, schema = ?, description = ?, plan = ?, status = ?
|
"UPDATE relays SET name = ?, subdomain = ?, schema = ?, icon = ?, description = ?, plan = ?, status = ?
|
||||||
WHERE id = ?",
|
WHERE id = ?",
|
||||||
)
|
)
|
||||||
.bind(&relay.name)
|
.bind(&relay.name)
|
||||||
.bind(&relay.subdomain)
|
.bind(&relay.subdomain)
|
||||||
.bind(&relay.schema)
|
.bind(&relay.schema)
|
||||||
|
.bind(&relay.icon)
|
||||||
.bind(&relay.description)
|
.bind(&relay.description)
|
||||||
.bind(&relay.plan)
|
.bind(&relay.plan)
|
||||||
.bind(&relay.status)
|
.bind(&relay.status)
|
||||||
@@ -100,9 +102,18 @@ impl Repo {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn update_relay_status(&self, id: &str, status: &str) -> Result<()> {
|
||||||
|
sqlx::query("UPDATE relays SET status = ? WHERE id = ?")
|
||||||
|
.bind(status)
|
||||||
|
.bind(id)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_relay(&self, id: &str) -> Result<Option<Relay>> {
|
pub async fn get_relay(&self, id: &str) -> Result<Option<Relay>> {
|
||||||
let relay = sqlx::query_as::<_, Relay>(
|
let relay = sqlx::query_as::<_, Relay>(
|
||||||
"SELECT id, tenant, name, subdomain, schema, description, plan, status FROM relays WHERE id = ?",
|
"SELECT id, tenant, name, subdomain, schema, icon, description, plan, status FROM relays WHERE id = ?",
|
||||||
)
|
)
|
||||||
.bind(id)
|
.bind(id)
|
||||||
.fetch_optional(&self.pool)
|
.fetch_optional(&self.pool)
|
||||||
@@ -112,7 +123,7 @@ impl Repo {
|
|||||||
|
|
||||||
pub async fn list_relays_by_tenant(&self, tenant: &str) -> Result<Vec<Relay>> {
|
pub async fn list_relays_by_tenant(&self, tenant: &str) -> Result<Vec<Relay>> {
|
||||||
let relays = sqlx::query_as::<_, Relay>(
|
let relays = sqlx::query_as::<_, Relay>(
|
||||||
"SELECT id, tenant, name, subdomain, schema, description, plan, status FROM relays WHERE tenant = ? ORDER BY name",
|
"SELECT id, tenant, name, subdomain, schema, icon, description, plan, status FROM relays WHERE tenant = ? ORDER BY name",
|
||||||
)
|
)
|
||||||
.bind(tenant)
|
.bind(tenant)
|
||||||
.fetch_all(&self.pool)
|
.fetch_all(&self.pool)
|
||||||
@@ -122,7 +133,7 @@ impl Repo {
|
|||||||
|
|
||||||
pub async fn list_relays(&self) -> Result<Vec<Relay>> {
|
pub async fn list_relays(&self) -> Result<Vec<Relay>> {
|
||||||
let relays = sqlx::query_as::<_, Relay>(
|
let relays = sqlx::query_as::<_, Relay>(
|
||||||
"SELECT id, tenant, name, subdomain, schema, description, plan, status FROM relays ORDER BY name",
|
"SELECT id, tenant, name, subdomain, schema, icon, description, plan, status FROM relays ORDER BY name",
|
||||||
)
|
)
|
||||||
.fetch_all(&self.pool)
|
.fetch_all(&self.pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
Reference in New Issue
Block a user