forked from coracle/caravel
7.1 KiB
7.1 KiB
pub struct Api
Api manages the HTTP interface for the application
Members:
host: String- the hostname of the service for checking NIP 98 auth, fromHOSTadmins: Vec<String>- a list of admin pubkeys fromADMINSquery: Querycommand: Commandbilling: Billing
Notes:
- Authentication is done using NIP 98 comparing
utoself.host, not the incoming request - Each route is responsible for authorization using
self.require_adminorself.require_admin_or_tenant - Successful API responses should be of the form
{data, code: "ok"}with an appropriate http status code. - Unsuccessful API responses should be of the form
{error, code}with an appropriate http status code.codeis a short error code (e.g.duplicate-subdomain) anderroris a human-readable error message.
pub fn new() -> Self
- Reads environment and populates members
pub fn router(&self) -> Result<()>
- Returns an
axum::Router
--- Plan routes
async fn list_plans(...) -> Response
- Serves
GET /plans - No authentication required
- Return
datais a list of plan structs fromQuery::list_plans
async fn get_plan(...) -> Response
- Serves
GET /plans/:id - No authentication required
- Return
datais a single plan struct matchingid - If plan does not exist, return
404withcode=not-found
--- Identity routes
async fn get_identity(...) -> Response
- Serves
GET /identity - Authorizes anyone, but must be authorized
- If a tenant for the identity doesn't exist:
- Call the Stripe API to create a new customer
- Create a new tenant using
command.create_tenantwith payload andstripe_customer_id. No subscription is created yet — that happens when the first relay is added.
- Return
datais anIdentitystruct
--- Tenant routes
async fn list_tenants(...) -> Response
- Serves
GET /tenants - Authorizes admin only
- Return
datais a list of tenant structs fromquery.list_tenants
async fn get_tenant(...) -> Response
- Serves
GET /tenants/:pubkey - Authorizes admin or matching tenant
- Return
datais a single tenant struct fromquery.get_tenant
async fn update_tenant(...) -> Response
- Serves
PUT /tenants/:pubkey - Authorizes admin or matching tenant
- Updates tenant using
command.update_tenant - Return
datais the updated tenant struct
async fn list_tenant_relays(...) -> Response
- Serves
GET /tenants/:pubkey/relays - Authorizes admin or matching tenant
- Return
datais a list of relay structs fromquery.list_relays_for_tenant
--- Relay routes
async fn list_relays(...) -> Response
- Serves
GET /relays - Authorizes admin only
- Return
datais a list of relay structs fromquery.list_relays
async fn get_relay(...) -> Response
- Serves
GET /relays/:id - Authorizes admin or relay owner
- Return
datais a single relay struct fromquery.get_relay
async fn create_relay(...) -> Response
- Serves
POST /relays - Authorizes admin or matching tenant pubkey in request body
- Validates/prepares the relay data to be saved using
prepare_relay - Creates a new relay using
command.create_relay - If relay is a duplicate by subdomain, return a
422withcode=subdomain-exists - Return
datais a single relay struct. Use HTTP201.
async fn update_relay(...) -> Response
- Serves
PUT /relays/:id - Authorizes admin or relay owner
- Validates/prepares the relay data to be saved using
prepare_relay - Updates the given relay using
command.update_relay - If relay is a duplicate by subdomain, return a
422withcode=subdomain-exists - Return
datais a single relay struct.
async fn list_relay_activity(...) -> Response
- Serves
GET /relays/:id/activity - Authorizes admin or relay owner
- Get activity from
query.list_activity_for_relay - Return
datais{activity}
async fn deactivate_relay(...) -> Response
- Serves
POST /relays/:id/deactivate - Authorizes admin or relay owner
- If relay is already inactive, return a
400withcode=relay-is-inactive - Call
command.deactivate_relay - Return
datais empty
async fn reactivate_relay(...) -> Response
- Serves
POST /relays/:id/reactivate - Authorizes admin or relay owner
- If relay is already active, return a
400withcode=relay-is-active - Call
command.activate_relay - Return
datais empty
--- Invoice routes
async fn list_tenant_invoices(...) -> Response
- Serves
GET /tenants/:pubkey/invoices - Authorizes admin or matching tenant
- Looks up tenant by pubkey, fetches invoices from Stripe API using
stripe_customer_id - Return
datais a list of Stripe invoice objects:{ id, status, amount_due, currency, hosted_invoice_url, period_start, period_end }
async fn get_invoice(...) -> Response
- Serves
GET /invoices/:id - Fetches invoice from Stripe API by ID
- Looks up tenant by the invoice's
customerfield, authorizes admin or matching tenant - Return
datais a single Stripe invoice object - If invoice does not exist, return
404withcode=not-found
async fn get_invoice_bolt11(...) -> Response
- Serves
GET /invoices/:id/bolt11 - Fetches invoice from Stripe API by ID
- Looks up tenant by the invoice's
customerfield, authorizes admin or matching tenant - If invoice
statusis notopen, return400withcode=invoice-not-open - Creates a bolt11 Lightning invoice for the invoice's
amount_dueusingbilling.create_bolt11(amount_due) - Return
datais{ bolt11 }
--- Stripe session route
async fn create_stripe_session(...) -> Response
- Serves
GET /tenants/:pubkey/stripe/session - Authorizes admin or matching tenant
- Looks up tenant by pubkey
- Creates a Stripe Customer Portal session for the tenant's
stripe_customer_id - Return
datais{ url }— the portal session URL
--- Stripe webhook route
async fn stripe_webhook(...) -> Response
- Serves
POST /stripe/webhook - No NIP-98 authentication — uses Stripe signature verification instead
- Reads raw request body and
Stripe-Signatureheader - Calls
billing.handle_webhook(payload, signature) - Returns
200on success,400on signature verification failure
--- Utilities
extract_auth_pubkey(&self, headers: &HeaderMap) -> Result<String>
- Parses
Authorizationheader - Validates event kind and signature using
nostr_sdk - Validates event
uagainstHOST(not the request path. Non-standard, but correct) - Does not validate
methodtag - Returns pubkey if header all checks pass
Refer to https://github.com/nostr-protocol/nips/blob/master/98.md for details. Use nostr_sdk functionality where possible.
require_admin(&self, authorized_pubkey: &str)
- Checks whether
authorized_pubkeyis inself.admins. If not, returns an forbidden error
require_admin_or_tenant(&self, authorized_pubkey: &str, tenant_pubkey: &str)
- Checks whether
authorized_pubkeyis an admin or matchestenant_pubkey
prepare_relay(&self, relay: Relay) -> anyhow::Result<Relay>
- Validate
subdomain - If
planis free andblossomis enabled, returnpremium-feature - If
planis free andlivekitis enabled, returnpremium-feature - Populate
schemaif not already set - Populate missing fields using reasonable defaults