Lint, format
This commit is contained in:
+106
-21
@@ -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<Relay> {
|
||||
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<String> {
|
||||
fn extract_auth_pubkey(
|
||||
headers: &HeaderMap,
|
||||
method: &Method,
|
||||
_uri: &Uri,
|
||||
host: &str,
|
||||
) -> Result<String> {
|
||||
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(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
+20
-6
@@ -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<nostr_sdk::nips::nip47::Response> {
|
||||
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();
|
||||
|
||||
+39
-33
@@ -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<Self> {
|
||||
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<i64> {
|
||||
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<i64> {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -45,11 +45,9 @@ export default function Account() {
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<h2 class="text-lg font-semibold text-gray-900">Account Status</h2>
|
||||
<Show when={tenant()}>
|
||||
{(t) => (
|
||||
<span class="rounded-full border border-gray-300 bg-gray-100 px-2.5 py-1 text-xs font-medium uppercase tracking-wide text-gray-700">
|
||||
tenant
|
||||
</span>
|
||||
)}
|
||||
<span class="rounded-full border border-gray-300 bg-gray-100 px-2.5 py-1 text-xs font-medium uppercase tracking-wide text-gray-700">
|
||||
tenant
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -30,13 +30,11 @@ export default function AdminTenantDetail() {
|
||||
<section class="bg-white border border-gray-200 rounded-xl p-6">
|
||||
<h2 class="text-lg font-semibold mb-4">Status</h2>
|
||||
<Show when={detail()}>
|
||||
{(d) => (
|
||||
<div class="space-y-3">
|
||||
<p class="text-sm text-gray-700">
|
||||
Current: <span class="font-medium uppercase tracking-wide">tenant</span>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<div class="space-y-3">
|
||||
<p class="text-sm text-gray-700">
|
||||
Current: <span class="font-medium uppercase tracking-wide">tenant</span>
|
||||
</p>
|
||||
</div>
|
||||
</Show>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user