From 087405b1ac2cc8c4a5f5ef8b484de78644b7b12c Mon Sep 17 00:00:00 2001 From: Jon Staab Date: Thu, 26 Mar 2026 10:56:42 -0700 Subject: [PATCH] Lint, format --- backend/src/api.rs | 127 +++++++++++++++--- backend/src/billing.rs | 26 +++- backend/src/repo.rs | 72 +++++----- backend/src/robot.rs | 16 +-- frontend/src/pages/Account.tsx | 8 +- .../src/pages/admin/AdminTenantDetail.tsx | 12 +- justfile | 11 +- 7 files changed, 191 insertions(+), 81 deletions(-) diff --git a/backend/src/api.rs b/backend/src/api.rs index 070a7af..f039dd2 100644 --- a/backend/src/api.rs +++ b/backend/src/api.rs @@ -1,7 +1,6 @@ use std::sync::Arc; use anyhow::{Result, anyhow}; -use base64::Engine; use axum::{ Json, Router, extract::{Path, Query, State}, @@ -9,6 +8,7 @@ use axum::{ response::{IntoResponse, Response}, routing::{get, post, put}, }; +use base64::Engine; use nostr_sdk::{Event, JsonUtil, Kind}; use serde::{Deserialize, Serialize}; use tower_http::cors::{AllowOrigin, CorsLayer}; @@ -86,7 +86,8 @@ impl Api { .with_state(state) .layer(self.cors_layer()); - let listener = tokio::net::TcpListener::bind(format!("{}:{}", self.host, self.port)).await?; + let listener = + tokio::net::TcpListener::bind(format!("{}:{}", self.host, self.port)).await?; axum::serve(listener, app).await?; Ok(()) } @@ -162,13 +163,18 @@ fn prepare_relay(mut relay: Relay) -> anyhow::Result { if relay.status.is_empty() { relay.status = "new".to_string(); } - relay.sync_error = relay.sync_error; relay.policy_public_join = parse_bool_default(relay.policy_public_join, 0); relay.policy_strip_signatures = parse_bool_default(relay.policy_strip_signatures, 0); relay.groups_enabled = parse_bool_default(relay.groups_enabled, 1); relay.management_enabled = parse_bool_default(relay.management_enabled, 1); - relay.blossom_enabled = parse_bool_default(relay.blossom_enabled, if relay.plan == "free" { 0 } else { 1 }); - relay.livekit_enabled = parse_bool_default(relay.livekit_enabled, if relay.plan == "free" { 0 } else { 1 }); + relay.blossom_enabled = parse_bool_default( + relay.blossom_enabled, + if relay.plan == "free" { 0 } else { 1 }, + ); + relay.livekit_enabled = parse_bool_default( + relay.livekit_enabled, + if relay.plan == "free" { 0 } else { 1 }, + ); relay.push_enabled = parse_bool_default(relay.push_enabled, 1); Ok(relay) @@ -192,7 +198,12 @@ fn auth_fail_response(e: anyhow::Error) -> Response { err(StatusCode::UNAUTHORIZED, "unauthorized", &e.to_string()) } -fn extract_auth_pubkey(headers: &HeaderMap, method: &Method, _uri: &Uri, host: &str) -> Result { +fn extract_auth_pubkey( + headers: &HeaderMap, + method: &Method, + _uri: &Uri, + host: &str, +) -> Result { let auth = headers .get(axum::http::header::AUTHORIZATION) .and_then(|v| v.to_str().ok()) @@ -304,7 +315,11 @@ async fn list_tenants( } match state.api.repo.list_tenants().await { Ok(tenants) => ok(StatusCode::OK, tenants), - Err(e) => err(StatusCode::INTERNAL_SERVER_ERROR, "internal", &e.to_string()), + Err(e) => err( + StatusCode::INTERNAL_SERVER_ERROR, + "internal", + &e.to_string(), + ), } } @@ -325,7 +340,11 @@ async fn get_tenant( match state.api.repo.get_tenant(&pubkey).await { Ok(Some(tenant)) => ok(StatusCode::OK, tenant), Ok(None) => err(StatusCode::NOT_FOUND, "not-found", "tenant not found"), - Err(e) => err(StatusCode::INTERNAL_SERVER_ERROR, "internal", &e.to_string()), + Err(e) => err( + StatusCode::INTERNAL_SERVER_ERROR, + "internal", + &e.to_string(), + ), } } @@ -351,9 +370,17 @@ async fn create_tenant( Ok(()) => ok(StatusCode::CREATED, tenant), Err(e) => { if matches!(map_unique_error(&e), Some("pubkey-exists")) { - err(StatusCode::UNPROCESSABLE_ENTITY, "pubkey-exists", "tenant already exists") + err( + StatusCode::UNPROCESSABLE_ENTITY, + "pubkey-exists", + "tenant already exists", + ) } else { - err(StatusCode::INTERNAL_SERVER_ERROR, "internal", &e.to_string()) + err( + StatusCode::INTERNAL_SERVER_ERROR, + "internal", + &e.to_string(), + ) } } } @@ -374,7 +401,15 @@ async fn list_relays( let tenant_filter = if state.api.is_admin(&auth) { query.tenant.as_deref() } else { - if state.api.repo.get_tenant(&auth).await.ok().flatten().is_none() { + if state + .api + .repo + .get_tenant(&auth) + .await + .ok() + .flatten() + .is_none() + { return err(StatusCode::FORBIDDEN, "forbidden", "tenant required"); } if query.tenant.is_some() { @@ -389,7 +424,11 @@ async fn list_relays( match state.api.repo.list_relays(tenant_filter).await { Ok(relays) => ok(StatusCode::OK, relays), - Err(e) => err(StatusCode::INTERNAL_SERVER_ERROR, "internal", &e.to_string()), + Err(e) => err( + StatusCode::INTERNAL_SERVER_ERROR, + "internal", + &e.to_string(), + ), } } @@ -408,7 +447,13 @@ async fn get_relay( let relay = match state.api.repo.get_relay(&id).await { Ok(Some(r)) => r, Ok(None) => return err(StatusCode::NOT_FOUND, "not-found", "relay not found"), - Err(e) => return err(StatusCode::INTERNAL_SERVER_ERROR, "internal", &e.to_string()), + Err(e) => { + return err( + StatusCode::INTERNAL_SERVER_ERROR, + "internal", + &e.to_string(), + ); + } }; if !(state.api.is_admin(&auth) || state.api.is_tenant(&auth, &relay.tenant)) { @@ -482,7 +527,11 @@ async fn create_relay( "subdomain already exists", ) } else { - err(StatusCode::INTERNAL_SERVER_ERROR, "internal", &e.to_string()) + err( + StatusCode::INTERNAL_SERVER_ERROR, + "internal", + &e.to_string(), + ) } } } @@ -504,7 +553,13 @@ async fn update_relay( let mut relay = match state.api.repo.get_relay(&id).await { Ok(Some(r)) => r, Ok(None) => return err(StatusCode::NOT_FOUND, "not-found", "relay not found"), - Err(e) => return err(StatusCode::INTERNAL_SERVER_ERROR, "internal", &e.to_string()), + Err(e) => { + return err( + StatusCode::INTERNAL_SERVER_ERROR, + "internal", + &e.to_string(), + ); + } }; if !(state.api.is_admin(&auth) || state.api.is_tenant(&auth, &relay.tenant)) { @@ -576,7 +631,11 @@ async fn update_relay( "subdomain already exists", ) } else { - err(StatusCode::INTERNAL_SERVER_ERROR, "internal", &e.to_string()) + err( + StatusCode::INTERNAL_SERVER_ERROR, + "internal", + &e.to_string(), + ) } } } @@ -597,7 +656,13 @@ async fn deactivate_relay( let relay = match state.api.repo.get_relay(&id).await { Ok(Some(r)) => r, Ok(None) => return err(StatusCode::NOT_FOUND, "not-found", "relay not found"), - Err(e) => return err(StatusCode::INTERNAL_SERVER_ERROR, "internal", &e.to_string()), + Err(e) => { + return err( + StatusCode::INTERNAL_SERVER_ERROR, + "internal", + &e.to_string(), + ); + } }; if !(state.api.is_admin(&auth) || state.api.is_tenant(&auth, &relay.tenant)) { @@ -606,7 +671,11 @@ async fn deactivate_relay( match state.api.repo.deactivate_relay(&relay).await { Ok(()) => ok(StatusCode::OK, ()), - Err(e) => err(StatusCode::INTERNAL_SERVER_ERROR, "internal", &e.to_string()), + Err(e) => err( + StatusCode::INTERNAL_SERVER_ERROR, + "internal", + &e.to_string(), + ), } } @@ -625,7 +694,15 @@ async fn list_invoices( let tenant_filter = if state.api.is_admin(&auth) { query.tenant.as_deref() } else { - if state.api.repo.get_tenant(&auth).await.ok().flatten().is_none() { + if state + .api + .repo + .get_tenant(&auth) + .await + .ok() + .flatten() + .is_none() + { return err(StatusCode::FORBIDDEN, "forbidden", "tenant required"); } if query.tenant.is_some() { @@ -640,7 +717,11 @@ async fn list_invoices( match state.api.repo.list_invoices(tenant_filter).await { Ok(invoices) => ok(StatusCode::OK, invoices), - Err(e) => err(StatusCode::INTERNAL_SERVER_ERROR, "internal", &e.to_string()), + Err(e) => err( + StatusCode::INTERNAL_SERVER_ERROR, + "internal", + &e.to_string(), + ), } } @@ -668,6 +749,10 @@ async fn update_tenant_billing( .await { Ok(()) => ok(StatusCode::OK, payload), - Err(e) => err(StatusCode::INTERNAL_SERVER_ERROR, "internal", &e.to_string()), + Err(e) => err( + StatusCode::INTERNAL_SERVER_ERROR, + "internal", + &e.to_string(), + ), } } diff --git a/backend/src/billing.rs b/backend/src/billing.rs index fcf67f0..cdda2af 100644 --- a/backend/src/billing.rs +++ b/backend/src/billing.rs @@ -42,7 +42,10 @@ impl Billing { let since = *since_guard; let activity = self.repo.list_activity(&since, None).await?; for a in &activity { - if matches!(a.activity_type.as_str(), "create_relay" | "update_relay" | "activate_relay") { + if matches!( + a.activity_type.as_str(), + "create_relay" | "update_relay" | "activate_relay" + ) { self.maybe_reset_anchor_for_first_paid_relay(a).await?; } *since_guard = (*since_guard).max(a.created_at); @@ -83,7 +86,12 @@ impl Billing { } async fn generate_invoice_if_due(&self, tenant: &Tenant) -> Result<()> { - if self.repo.total_pending_invoices_for_tenant(&tenant.pubkey).await? > 0 { + if self + .repo + .total_pending_invoices_for_tenant(&tenant.pubkey) + .await? + > 0 + { return Ok(()); } @@ -104,12 +112,16 @@ impl Billing { return Ok(()); } - let usage_events = self.repo.list_activity(&tenant.billing_anchor, Some(&tenant.pubkey)).await?; + let usage_events = self + .repo + .list_activity(&tenant.billing_anchor, Some(&tenant.pubkey)) + .await?; let invoice_id = uuid::Uuid::new_v4().to_string(); let mut items = Vec::new(); for relay in active_paid_relays { - let hours = relay_active_hours_in_window(&relay, &usage_events, period_start, period_end); + let hours = + relay_active_hours_in_window(&relay, &usage_events, period_start, period_end); if hours <= 0 { continue; } @@ -178,7 +190,9 @@ impl Billing { } let mut collected = false; - if !tenant.nwc_url.trim().is_empty() && self.pay_invoice_nwc(&tenant.nwc_url, &invoice.bolt11).await { + if !tenant.nwc_url.trim().is_empty() + && self.pay_invoice_nwc(&tenant.nwc_url, &invoice.bolt11).await + { self.repo.mark_invoice_paid(&invoice.id).await?; collected = true; } @@ -256,7 +270,7 @@ impl Billing { uri: &nostr_sdk::nips::nip47::NostrWalletConnectURI, request: nostr_sdk::nips::nip47::Request, ) -> Result { - use nostr_sdk::{Client, Filter, Kind, Keys, Timestamp}; + use nostr_sdk::{Client, Filter, Keys, Kind, Timestamp}; let app_keys = Keys::new(uri.secret.clone()); let app_pubkey = app_keys.public_key(); diff --git a/backend/src/repo.rs b/backend/src/repo.rs index 93de9cb..aaacb1d 100644 --- a/backend/src/repo.rs +++ b/backend/src/repo.rs @@ -1,7 +1,11 @@ use std::path::Path; +use std::str::FromStr; use anyhow::Result; -use sqlx::{Sqlite, SqlitePool, Transaction, sqlite::SqlitePoolOptions}; +use sqlx::{ + Sqlite, SqlitePool, Transaction, + sqlite::{SqliteConnectOptions, SqlitePoolOptions}, +}; use crate::models::{Activity, Invoice, InvoiceItem, Relay, Tenant}; @@ -12,8 +16,10 @@ pub struct Repo { impl Repo { pub async fn new() -> Result { - let database_url = std::env::var("DATABASE_URL") - .unwrap_or_else(|_| "sqlite://data/caravel.db".to_string()); + let raw_database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| { + format!("sqlite://{}/data/caravel.db", env!("CARGO_MANIFEST_DIR")) + }); + let database_url = normalize_sqlite_url(&raw_database_url); if let Some(path) = database_url.strip_prefix("sqlite://") && !path.is_empty() @@ -24,9 +30,11 @@ impl Repo { std::fs::create_dir_all(parent)?; } + let connect_options = SqliteConnectOptions::from_str(&database_url)?.create_if_missing(true); + let pool = SqlitePoolOptions::new() .max_connections(5) - .connect(&database_url) + .connect_with(connect_options) .await?; sqlx::query("PRAGMA journal_mode = WAL;") @@ -118,7 +126,11 @@ impl Repo { Ok(()) } - pub async fn update_tenant_billing_anchor(&self, pubkey: &str, billing_anchor: i64) -> Result<()> { + pub async fn update_tenant_billing_anchor( + &self, + pubkey: &str, + billing_anchor: i64, + ) -> Result<()> { let mut tx = self.pool.begin().await?; sqlx::query("UPDATE tenant SET billing_anchor = ? WHERE pubkey = ?") @@ -284,20 +296,6 @@ impl Repo { Ok(()) } - pub async fn activate_relay(&self, relay: &Relay) -> Result<()> { - let mut tx = self.pool.begin().await?; - - sqlx::query("UPDATE relay SET status = 'active' WHERE id = ?") - .bind(&relay.id) - .execute(&mut *tx) - .await?; - - Self::insert_activity(&mut tx, "activate_relay", "relay", &relay.id).await?; - - tx.commit().await?; - Ok(()) - } - pub async fn fail_relay_sync(&self, relay: &Relay, sync_error: String) -> Result<()> { let mut tx = self.pool.begin().await?; @@ -313,7 +311,11 @@ impl Repo { Ok(()) } - pub async fn create_invoice(&self, invoice: &Invoice, invoice_items: &[InvoiceItem]) -> Result<()> { + pub async fn create_invoice( + &self, + invoice: &Invoice, + invoice_items: &[InvoiceItem], + ) -> Result<()> { let mut tx = self.pool.begin().await?; sqlx::query( @@ -396,7 +398,11 @@ impl Repo { Ok(()) } - pub async fn mark_invoice_attempted(&self, invoice_id: &str, error: Option<&str>) -> Result<()> { + pub async fn mark_invoice_attempted( + &self, + invoice_id: &str, + error: Option<&str>, + ) -> Result<()> { let mut tx = self.pool.begin().await?; sqlx::query( @@ -485,17 +491,6 @@ impl Repo { Ok(rows) } - pub async fn total_active_paid_relays_for_tenant(&self, tenant: &str) -> Result { - let count = sqlx::query_scalar::<_, i64>( - "SELECT COUNT(*) FROM relay - WHERE tenant = ? AND status = 'active' AND plan != 'free'", - ) - .bind(tenant) - .fetch_one(&self.pool) - .await?; - Ok(count) - } - pub async fn total_pending_invoices_for_tenant(&self, tenant: &str) -> Result { let count = sqlx::query_scalar::<_, i64>( "SELECT COUNT(*) FROM invoice @@ -516,5 +511,16 @@ impl Repo { }; Ok(sats) } - +} + +fn normalize_sqlite_url(url: &str) -> String { + let Some(path) = url.strip_prefix("sqlite://") else { + return url.to_string(); + }; + + if path.is_empty() || path == ":memory:" || Path::new(path).is_absolute() { + return url.to_string(); + } + + format!("sqlite://{}/{}", env!("CARGO_MANIFEST_DIR"), path) } diff --git a/backend/src/robot.rs b/backend/src/robot.rs index 3edf51f..fbb3977 100644 --- a/backend/src/robot.rs +++ b/backend/src/robot.rs @@ -111,7 +111,9 @@ impl Robot { return Err(anyhow!("no outbox relays found for recipient")); } - let dm_relays = self.fetch_messaging_relays_from_outbox(recipient, &outbox).await?; + let dm_relays = self + .fetch_messaging_relays_from_outbox(recipient, &outbox) + .await?; if dm_relays.is_empty() { return Err(anyhow!("no messaging relays found for recipient")); } @@ -123,7 +125,9 @@ impl Robot { client.add_relay(relay).await?; } client.connect().await; - client.send_private_msg(recipient_pubkey, message, []).await?; + client + .send_private_msg(recipient_pubkey, message, []) + .await?; Ok(()) } @@ -135,9 +139,7 @@ impl Robot { let pubkey = PublicKey::parse(recipient)?; let client = indexer_client(&self.secret, &self.indexer_relays).await?; let filter = Filter::new().author(pubkey).kind(Kind::Custom(10002)); - let events = client - .fetch_events(filter, Duration::from_secs(5)) - .await?; + let events = client.fetch_events(filter, Duration::from_secs(5)).await?; let mut relays = Vec::new(); if let Some(event) = events.into_iter().max_by_key(|e| e.created_at) { @@ -171,9 +173,7 @@ impl Robot { client.connect().await; let filter = Filter::new().author(pubkey).kind(Kind::Custom(10050)); - let events = client - .fetch_events(filter, Duration::from_secs(5)) - .await?; + let events = client.fetch_events(filter, Duration::from_secs(5)).await?; let mut relays = Vec::new(); if let Some(event) = events.into_iter().max_by_key(|e| e.created_at) { diff --git a/frontend/src/pages/Account.tsx b/frontend/src/pages/Account.tsx index c5dd54a..55e3ce1 100644 --- a/frontend/src/pages/Account.tsx +++ b/frontend/src/pages/Account.tsx @@ -45,11 +45,9 @@ export default function Account() {

Account Status

- {(t) => ( - - tenant - - )} + + tenant +
diff --git a/frontend/src/pages/admin/AdminTenantDetail.tsx b/frontend/src/pages/admin/AdminTenantDetail.tsx index fc6e613..bbfba3f 100644 --- a/frontend/src/pages/admin/AdminTenantDetail.tsx +++ b/frontend/src/pages/admin/AdminTenantDetail.tsx @@ -30,13 +30,11 @@ export default function AdminTenantDetail() {

Status

- {(d) => ( -
-

- Current: tenant -

-
- )} +
+

+ Current: tenant +

+
diff --git a/justfile b/justfile index 0035973..39ec917 100644 --- a/justfile +++ b/justfile @@ -5,6 +5,15 @@ dev: cd frontend && bun dev & wait +dev-frontend: + cd frontend && bun run dev + +build-frontend: + cd frontend && bun run build + +preview-frontend: + cd frontend && bun run preview + fmt-backend: cd backend && cargo fmt @@ -18,6 +27,6 @@ lint: lint-backend build-backend: cd backend && cargo build -build: build-backend +build: build-backend build-frontend check: fmt lint build