Update readme, move frontend build to build phase in dockerfile
This commit is contained in:
+14
-15
@@ -16,17 +16,16 @@ Rust backend for Caravel. It manages tenants, relays, invoices, and background w
|
||||
backend/
|
||||
migrations/
|
||||
0001_init.sql
|
||||
spec/ # Module-by-module design notes
|
||||
src/
|
||||
main.rs # App bootstrap: load Env, build services, serve + spawn workers
|
||||
env.rs # Configuration from the environment (+ NIP-44 encryption, NIP-98 signing)
|
||||
api.rs # Shared Api state, router, NIP-98 auth + authorization helpers
|
||||
web.rs # HTTP response envelope + helpers
|
||||
routes/ # HTTP route handlers (identity, plans, tenants, relays, invoices, stripe)
|
||||
routes/ # HTTP route handlers (identity, plans, tenants, relays, invoices)
|
||||
models.rs # Domain models + sqlite rows
|
||||
query.rs # Database reads
|
||||
command.rs # Database writes + activity broadcast
|
||||
pool.rs # SQLite pool + migrations
|
||||
db.rs # SQLite pool, migrations, activity broadcast channel
|
||||
billing.rs # Stripe subscription reconciliation + Lightning collection worker
|
||||
stripe.rs # Thin Stripe REST client
|
||||
wallet.rs # NWC wallet handle (NIP-47)
|
||||
@@ -43,10 +42,11 @@ All configuration is read from the environment by `Env::load()` at startup. **Ev
|
||||
|
||||
| Variable | Description |
|
||||
| ---------------------- | ------------------------------------------------------------------- |
|
||||
| `SERVER_HOST` | API bind host; also the value the NIP-98 `u` tag must contain |
|
||||
| `SERVER_PORT` | API bind port |
|
||||
| `SERVER_URL` | Public API base URL; the value the NIP-98 `u` tag must equal exactly |
|
||||
| `SERVER_PORT` | API bind port (the server always binds to `127.0.0.1`) |
|
||||
| `SERVER_ADMIN_PUBKEYS` | Comma-separated admin pubkeys (hex) |
|
||||
| `SERVER_ALLOW_ORIGINS` | Comma-separated allowed CORS origins |
|
||||
| `APP_URL` | Frontend base URL; used to build links in DMs and invoices |
|
||||
| `DATABASE_URL` | SQLite URL; relative `sqlite://` paths are resolved under `backend/` |
|
||||
|
||||
**Robot identity**
|
||||
@@ -84,18 +84,15 @@ All configuration is read from the environment by `Env::load()` at startup. **Ev
|
||||
|
||||
**Billing (Stripe)**
|
||||
|
||||
| Variable | Description |
|
||||
| ----------------------- | ----------------------------------------------------------------------- |
|
||||
| `STRIPE_SECRET_KEY` | Stripe API secret key used for billing API operations |
|
||||
| `STRIPE_WEBHOOK_SECRET` | Stripe webhook signing secret used to verify `Stripe-Signature` headers |
|
||||
| `STRIPE_PRICE_BASIC` | Stripe price ID (`price_...`) backing the Basic plan |
|
||||
| `STRIPE_PRICE_GROWTH` | Stripe price ID (`price_...`) backing the Growth plan |
|
||||
| Variable | Description |
|
||||
| ------------------- | ----------------------------------------------------- |
|
||||
| `STRIPE_SECRET_KEY` | Stripe API secret key used for billing API operations |
|
||||
|
||||
Comma-separated list variables are split on commas and trimmed; empty entries are dropped.
|
||||
|
||||
## Schema and Architecture
|
||||
|
||||
See [spec](spec) for more details
|
||||
The database schema lives in [migrations](migrations); see the module-level doc comments in `src/` for architecture details.
|
||||
|
||||
## API Routes
|
||||
|
||||
@@ -105,7 +102,6 @@ Public exceptions:
|
||||
|
||||
- `GET /plans`
|
||||
- `GET /plans/:id`
|
||||
- `POST /stripe/webhook` (validated with Stripe signatures)
|
||||
|
||||
- `GET /identity` — get auth identity (`pubkey`, `is_admin`); side-effect-free
|
||||
- `GET /tenants` — list tenants (admin)
|
||||
@@ -122,16 +118,19 @@ Public exceptions:
|
||||
- `POST /relays/:id/deactivate` — deactivate relay (admin or relay tenant)
|
||||
- `POST /relays/:id/reactivate` — reactivate relay (admin or relay tenant)
|
||||
- `GET /tenants/:pubkey/invoices` — list tenant invoices (admin or same tenant)
|
||||
- `GET /tenants/:pubkey/invoices/draft` — get the tenant's synthetic draft invoice for the current period, or `null` if nothing is due (admin or same tenant)
|
||||
- `GET /tenants/:pubkey/invoices/draft/items` — list the draft invoice's line items (admin or same tenant)
|
||||
- `GET /invoices/:id` — get invoice (admin or same tenant)
|
||||
- `GET /invoices/:id/bolt11` — get invoice bolt11 (admin or same tenant)
|
||||
- `GET /tenants/:pubkey/stripe/session` — create Stripe customer portal session (admin or same tenant)
|
||||
- `GET /invoices/:id/items` — list invoice line items (admin or same tenant)
|
||||
- `GET /tenants/:pubkey/stripe/session` — create Stripe customer portal session (same tenant only)
|
||||
|
||||
## API Auth Model
|
||||
|
||||
Caravel intentionally uses a session-style variant of NIP-98 for client-to-backend API auth.
|
||||
|
||||
- Frontend signs one kind `27235` event with `u = VITE_API_URL` and caches that header for about 10 minutes.
|
||||
- Backend verifies event kind, signature, and that `u` contains configured `SERVER_HOST`.
|
||||
- Backend verifies event kind, signature, and that `u` equals configured `SERVER_URL`.
|
||||
- Backend intentionally does not bind auth to exact request URL/method/query, and does not enforce payload hash, timestamp freshness window, or replay cache.
|
||||
- Goal: reduce repeated wallet signing prompts and avoid cookie-based sessions.
|
||||
- Tradeoff: this is weaker request-intent binding than strict NIP-98 semantics.
|
||||
|
||||
+2
-1
@@ -32,7 +32,7 @@ use crate::models::{Relay, Tenant};
|
||||
use crate::query;
|
||||
use crate::robot::Robot;
|
||||
use crate::routes::identity::get_identity;
|
||||
use crate::routes::invoices::{get_invoice, get_invoice_bolt11, list_invoice_items};
|
||||
use crate::routes::invoices::{get_invoice, get_invoice_bolt11, list_invoice_items, list_invoices};
|
||||
use crate::routes::plans::{get_plan, list_plans};
|
||||
use crate::routes::relays::{
|
||||
create_relay, deactivate_relay, get_relay, list_relay_activity, list_relay_members,
|
||||
@@ -87,6 +87,7 @@ impl Api {
|
||||
.route("/relays/:id/activity", get(list_relay_activity))
|
||||
.route("/relays/:id/deactivate", post(deactivate_relay))
|
||||
.route("/relays/:id/reactivate", post(reactivate_relay))
|
||||
.route("/invoices", get(list_invoices))
|
||||
.route("/invoices/:id", get(get_invoice))
|
||||
.route("/invoices/:id/bolt11", get(get_invoice_bolt11))
|
||||
.route("/invoices/:id/items", get(list_invoice_items))
|
||||
|
||||
@@ -131,7 +131,15 @@ pub async fn get_invoice(invoice_id: &str) -> Result<Option<Invoice>> {
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn list_invoices(tenant_pubkey: &str) -> Result<Vec<Invoice>> {
|
||||
pub async fn list_invoices() -> Result<Vec<Invoice>> {
|
||||
Ok(
|
||||
sqlx::query_as::<_, Invoice>("SELECT * FROM invoice ORDER BY created_at DESC")
|
||||
.fetch_all(pool())
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn list_invoices_for_tenant(tenant_pubkey: &str) -> Result<Vec<Invoice>> {
|
||||
Ok(sqlx::query_as::<_, Invoice>(
|
||||
"SELECT * FROM invoice WHERE tenant_pubkey = ? ORDER BY created_at DESC",
|
||||
)
|
||||
|
||||
@@ -6,6 +6,15 @@ use crate::api::{Api, AuthedPubkey};
|
||||
use crate::query;
|
||||
use crate::web::{ApiResult, internal, not_found, ok};
|
||||
|
||||
pub async fn list_invoices(
|
||||
State(api): State<Arc<Api>>,
|
||||
AuthedPubkey(auth): AuthedPubkey,
|
||||
) -> ApiResult {
|
||||
api.require_admin(&auth)?;
|
||||
|
||||
ok(query::list_invoices().await.map_err(internal)?)
|
||||
}
|
||||
|
||||
pub async fn get_invoice(
|
||||
State(api): State<Arc<Api>>,
|
||||
AuthedPubkey(auth): AuthedPubkey,
|
||||
|
||||
@@ -174,7 +174,9 @@ pub async fn list_tenant_invoices(
|
||||
.await
|
||||
.map_err(internal)?;
|
||||
|
||||
let invoices = query::list_invoices(&pubkey).await.map_err(internal)?;
|
||||
let invoices = query::list_invoices_for_tenant(&pubkey)
|
||||
.await
|
||||
.map_err(internal)?;
|
||||
|
||||
ok(invoices)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user