Separate command and query
This commit is contained in:
+13
-13
@@ -5,10 +5,10 @@ Api manages the HTTP interface for the application
|
||||
Members:
|
||||
|
||||
- `host: String` - the hostname of the service for checking NIP 98 auth, from `HOST`
|
||||
- `port: u16` - a port to run the server on from `PORT`
|
||||
- `admins: Vec<String>` - a list of admin pubkeys from `ADMINS`
|
||||
- `origins: Vec<String>` - to be used in CORS headers, from `ALLOW_ORIGINS`
|
||||
- `repo: Repo`
|
||||
- `query: Query`
|
||||
- `command: Command`
|
||||
- `billing: Billing`
|
||||
|
||||
Notes:
|
||||
|
||||
@@ -31,7 +31,7 @@ Notes:
|
||||
|
||||
- Serves `GET /plans`
|
||||
- No authentication required
|
||||
- Return `data` is a list of plan structs from `Repo::list_plans`
|
||||
- Return `data` is a list of plan structs from `Query::list_plans`
|
||||
|
||||
## `async fn get_plan(...) -> Response`
|
||||
|
||||
@@ -55,26 +55,26 @@ Notes:
|
||||
|
||||
- Serves `GET /tenants`
|
||||
- Authorizes admin only
|
||||
- Return `data` is a list of tenant structs from `repo.list_tenants`
|
||||
- Return `data` is a list of tenant structs from `query.list_tenants`
|
||||
|
||||
## `async fn get_tenant(...) -> Response`
|
||||
|
||||
- Serves `GET /tenants/:pubkey`
|
||||
- Authorizes admin or matching tenant
|
||||
- Return `data` is a single tenant struct from `repo.get_tenant`
|
||||
- Return `data` is a single tenant struct from `query.get_tenant`
|
||||
|
||||
## `async fn update_tenant(...) -> Response`
|
||||
|
||||
- Serves `PUT /tenants/:pubkey`
|
||||
- Authorizes admin or matching tenant
|
||||
- Updates tenant using `repo.update_tenant`
|
||||
- Updates tenant using `command.update_tenant`
|
||||
- Return `data` is the updated tenant struct
|
||||
|
||||
## `async fn list_tenant_relays(...) -> Response`
|
||||
|
||||
- Serves `GET /tenants/:pubkey/relays`
|
||||
- Authorizes admin or matching tenant
|
||||
- Return `data` is a list of relay structs from `repo.list_relays_for_tenant`
|
||||
- Return `data` is a list of relay structs from `query.list_relays_for_tenant`
|
||||
|
||||
--- Relay routes
|
||||
|
||||
@@ -82,20 +82,20 @@ Notes:
|
||||
|
||||
- Serves `GET /relays`
|
||||
- Authorizes admin only
|
||||
- Return `data` is a list of relay structs from `repo.list_relays`
|
||||
- Return `data` is a list of relay structs from `query.list_relays`
|
||||
|
||||
## `async fn get_relay(...) -> Response`
|
||||
|
||||
- Serves `GET /relays/:id`
|
||||
- Authorizes admin or relay owner
|
||||
- Return `data` is a single relay struct from `repo.get_relay`
|
||||
- Return `data` is a single relay struct from `query.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 `repo.create_relay`
|
||||
- Creates a new relay using `command.create_relay`
|
||||
- If relay is a duplicate by subdomain, return a `422` with `code=subdomain-exists`
|
||||
- Return `data` is a single relay struct. Use HTTP `201`.
|
||||
|
||||
@@ -104,7 +104,7 @@ Notes:
|
||||
- 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 `repo.update_relay`
|
||||
- Updates the given relay using `command.update_relay`
|
||||
- If relay is a duplicate by subdomain, return a `422` with `code=subdomain-exists`
|
||||
- Return `data` is a single relay struct.
|
||||
|
||||
@@ -112,7 +112,7 @@ Notes:
|
||||
|
||||
- Serves `GET /relays/:id/activity`
|
||||
- Authorizes admin or relay owner
|
||||
- Get activity from `repo.list_activity_for_relay`
|
||||
- Get activity from `query.list_activity_for_relay`
|
||||
- Return `data` is `{activity}`
|
||||
|
||||
## `async fn deactivate_relay(...) -> Response`
|
||||
|
||||
@@ -5,9 +5,10 @@ Billing encapsulates logic related to synchronizing state with Stripe.
|
||||
Members:
|
||||
|
||||
- `nwc_url: String` - a nostr wallet connect URL used to **create** bolt11 invoices
|
||||
- `repo: Repo`
|
||||
- `query: Query`
|
||||
- `command: Command`
|
||||
- `robot: Robot`
|
||||
|
||||
## `pub fn new(repo: Repo, robot: Robot) -> Self`
|
||||
## `pub fn new(query: Query, command: Command, robot: Robot) -> Self`
|
||||
|
||||
- Reads environment and populates members
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
# `pub struct Command`
|
||||
|
||||
Command writes to the database.
|
||||
|
||||
Members:
|
||||
|
||||
- `pool: SqlitePool` - a sqlite connection pool
|
||||
|
||||
Notes:
|
||||
|
||||
- All public write methods should be atomic
|
||||
- All writes should be accompanied by an activity log entry of `(tenant, activity_type, resource_type, resource_id)`
|
||||
|
||||
## `pub fn new(&self, pool: SqlitePool) -> Self`
|
||||
|
||||
- Assigns pool to self
|
||||
|
||||
## `pub fn create_tenant(&self, tenant: &Tenant) -> Result<()>`
|
||||
|
||||
- Creates tenant, may throw sqlite uniqueness error on pubkey
|
||||
- Logs activity as `(create_tenant, tenant_id)`
|
||||
|
||||
## `pub fn update_tenant(&self, tenant: &Tenant) -> Result<()>`
|
||||
|
||||
- Updates tenant
|
||||
- Logs activity as `(update_tenant, tenant_id)`
|
||||
|
||||
## `pub fn create_relay(&self, relay: &Relay) -> Result<()>`
|
||||
|
||||
- Creates relay, may throw sqlite uniqueness error on subdomain
|
||||
- Sets relay status to `new`
|
||||
- Logs activity as `(create_relay, relay_id)`
|
||||
|
||||
## `pub fn update_relay(&self, relay: &Relay) -> Result<()>`
|
||||
|
||||
- Updates relay, may throw sqlite uniqueness error on subdomain
|
||||
- Logs activity as `(update_relay, relay_id)`
|
||||
|
||||
## `pub fn deactivate_relay(&self, relay: &Relay) -> Result<()>`
|
||||
|
||||
- Sets relay status to `inactive`
|
||||
- Logs activity as `(deactivate_relay, relay_id)`
|
||||
|
||||
## `pub fn activate_relay(&self, relay: &Relay) -> Result<()>`
|
||||
|
||||
- Sets relay status to `active`
|
||||
- Logs activity as `(activate_relay, relay_id)`
|
||||
|
||||
## `pub fn fail_relay_sync(&self, relay: &Relay, sync_error: &str) -> Result<()>`
|
||||
|
||||
- Sets relay status to `inactive`, sets `sync_error`
|
||||
- Logs activity as `(fail_relay_sync, relay_id)`
|
||||
|
||||
## `pub fn mark_relay_synced(&self, relay_id: &str) -> Result<()>`
|
||||
|
||||
- Sets `synced = 1`, `status = 'active'`, clears `sync_error`
|
||||
- No activity log (called by infra after successful sync)
|
||||
@@ -5,25 +5,26 @@ Infra is a service which polls the database and synchronizes updates to relays t
|
||||
Members:
|
||||
|
||||
- `api_url: String` - the URL of the zooid instance to be managed, from `ZOOID_API_URL`
|
||||
- `repo: Repo`
|
||||
- `query: Query`
|
||||
- `command: Command`
|
||||
|
||||
## `pub fn new(repo: Repo) -> Self`
|
||||
## `pub fn new(query: Query, command: Command) -> Self`
|
||||
|
||||
- Reads environment and populates members
|
||||
|
||||
## `pub async fn start(self)`
|
||||
|
||||
- Initializes `last_activity_at` from `repo.max_activity_at()` so historical activities are not replayed on restart.
|
||||
- Initializes `last_activity_at` from `query.max_activity_at()` so historical activities are not replayed on restart.
|
||||
- Calls `self.tick` in a loop every 10 seconds.
|
||||
|
||||
## `pub async fn tick(self)`
|
||||
|
||||
Iterates over `repo.list_activity` since last run and does the following:
|
||||
Iterates over `query.list_activity` since last run and does the following:
|
||||
|
||||
- For `create_relay`, `update_relay`, or `deactivate_relay` activity, sync the relay to zooid.
|
||||
- Uses `relay.synced` to decide POST vs PUT (not the activity type), so already-synced relays always use PUT even on restart.
|
||||
- On success, calls `repo.mark_relay_synced` to set `synced = 1`, `status = 'active'`, and clear `sync_error`.
|
||||
- On failure, calls `repo.fail_relay_sync`.
|
||||
- On success, calls `command.mark_relay_synced` to set `synced = 1`, `status = 'active'`, and clear `sync_error`.
|
||||
- On failure, calls `command.fail_relay_sync`.
|
||||
- All other activity types are ignored (e.g. `fail_relay_sync` must not trigger another sync).
|
||||
|
||||
## `async fn sync_relay(&self, relay: &Relay, is_new: bool)`
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# `async fn main() -> Result<()>`
|
||||
|
||||
- Configures logging
|
||||
- Creates instances of `Repo`, `Robot`, `Billing`, `Api`, and `Infra`
|
||||
- Spawns `infra.start`
|
||||
- Calls `create_pool` to get a `SqlitePool`, then creates `Query`, `Command`, `Robot`, `Billing`, `Api`, and `Infra`
|
||||
- Get an axum router from `api.router`
|
||||
- Adds CORS middleware based on `origins`
|
||||
- Calls `axum::serve` with a listener
|
||||
- Spawns `infra.start`
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# `pub async fn create_pool() -> Result<SqlitePool>`
|
||||
|
||||
Creates and returns a sqlite connection pool.
|
||||
|
||||
Notes:
|
||||
|
||||
- Database table names are singular: `activity`, `tenant`, `relay`
|
||||
|
||||
Steps:
|
||||
|
||||
- Reads `DATABASE_URL` from environment
|
||||
- Ensures that any directories referred to in `DATABASE_URL` exist
|
||||
- Initializes the sqlx pool
|
||||
- Runs migrations found in the `migrations` directory
|
||||
@@ -0,0 +1,50 @@
|
||||
# `pub struct Query`
|
||||
|
||||
Query reads from the database.
|
||||
|
||||
Members:
|
||||
|
||||
- `pool: SqlitePool` - a sqlite connection pool
|
||||
|
||||
## `pub fn new(&self, pool: SqlitePool) -> Self`
|
||||
|
||||
- Assigns pool to self
|
||||
|
||||
## `pub fn list_tenants(&self) -> Result<Vec<Tenant>>`
|
||||
|
||||
- Returns all tenants
|
||||
|
||||
## `pub fn get_tenant(&self, pubkey: &str) -> Result<Tenant>`
|
||||
|
||||
- Returns matching tenant
|
||||
|
||||
## `pub fn list_plans() -> Vec<Plan>`
|
||||
|
||||
- Returns the hardcoded relay plans used by the system (`free`, `basic`, `growth`)
|
||||
- This is the source of truth for plan metadata exposed via API
|
||||
|
||||
## `pub fn list_relays(&self) -> Result<Vec<Relay>>`
|
||||
|
||||
- Returns all relays
|
||||
|
||||
## `pub fn list_relays_for_tenant(&self, tenant_id: &str) -> Result<Vec<Relay>>`
|
||||
|
||||
- Returns all relays belonging to the given tenant
|
||||
|
||||
## `pub fn get_relay(&self, id: &str) -> Result<Relay>`
|
||||
|
||||
- Returns matching relay
|
||||
|
||||
## `pub fn max_activity_at(&self) -> Result<i64>`
|
||||
|
||||
- Returns the maximum `created_at` value from the activity table, or 0 if empty
|
||||
- Used by infra to initialize the since guard on startup
|
||||
|
||||
## `pub fn list_activity(&self, since: &i64) -> Result<Vec<Activity>>`
|
||||
|
||||
- Returns all activity occuring after `since`
|
||||
|
||||
## `pub fn list_activity_for_relay(&self, relay_id: &str) -> Result<Vec<Activity>>`
|
||||
|
||||
- Returns all activity where `resource_type = 'relay'` and `resource_id = relay_id`
|
||||
- Ordered newest-first
|
||||
@@ -1,107 +0,0 @@
|
||||
# `pub struct Repo`
|
||||
|
||||
Repo is a wrapper around a sqlite pool which implements methods related to database access.
|
||||
|
||||
Members:
|
||||
|
||||
- `pool: sqlx::SqlitePool` - a sqlite connection pool
|
||||
|
||||
Notes:
|
||||
|
||||
- All public write methods should be run in a transaction so they're atomic
|
||||
- All writes should be accompanied by an activity log entry of `(tenant, activity_type, resource_type, resource_id)`
|
||||
- Database table names are singular: `activity`, `tenant`, `relay`
|
||||
|
||||
## `pub fn new() -> Self`
|
||||
|
||||
- Reads `DATABASE_URL` from environment
|
||||
- Ensures that any directories referred to in `DATABASE_URL` exist
|
||||
- Initializes its sqlx `pool`
|
||||
- Runs migrations found in the `migrations` directory.
|
||||
|
||||
## `fn insert_activity(activity_type, resource_type, resource_id) -> Result<()>`
|
||||
|
||||
- Private helper that inserts one row into `activity`
|
||||
- Infers `tenant` from `resource_type` and `resource_id`
|
||||
- Used by write methods to avoid repeating audit-log SQL
|
||||
|
||||
## `pub fn list_tenants(&self) -> Result<Vec<Tenant>>`
|
||||
|
||||
- Returns all tenants
|
||||
|
||||
## `pub fn get_tenant(&self, pubkey: &str) -> Result<Tenant>`
|
||||
|
||||
- Returns matching tenant
|
||||
|
||||
## `pub fn create_tenant(&self, tenant: &Tenant) -> Result<()>`
|
||||
|
||||
- Creates tenant, may throw sqlite uniqueness error on pubkey
|
||||
- Logs activity as `(create_tenant, tenant_id)`
|
||||
|
||||
## `pub fn update_tenant(&self, tenant: &Tenant) -> Result<()>`
|
||||
|
||||
- Updates tenant
|
||||
- Logs activity as `(update_tenant, tenant_id)`
|
||||
|
||||
## `pub fn list_plans() -> Vec<Plan>`
|
||||
|
||||
- Returns the hardcoded relay plans used by the system (`free`, `basic`, `growth`)
|
||||
- This is the source of truth for plan metadata exposed via API
|
||||
|
||||
## `pub fn list_relays(&self) -> Result<Vec<Relay>>`
|
||||
|
||||
- Returns all relays
|
||||
|
||||
## `pub fn list_relays_for_tenant(&self, tenant_id: &str) -> Result<Vec<Relay>>`
|
||||
|
||||
- Returns all relays belonging to the given tenant
|
||||
|
||||
## `pub fn get_relay(&self, id: &str) -> Result<Relay>`
|
||||
|
||||
- Returns matching relay
|
||||
|
||||
## `pub fn create_relay(&self, relay: &Relay) -> Result<()>`
|
||||
|
||||
- Creates relay, may throw sqlite uniqueness error on subdomain
|
||||
- Sets relay status to `new`
|
||||
- Logs activity as `(create_relay, relay_id)`
|
||||
|
||||
## `pub fn update_relay(&self, relay: &Relay) -> Result<()>`
|
||||
|
||||
- Updates relay, may throw sqlite uniqueness error on subdomain
|
||||
- Logs activity as `(update_relay, relay_id)`
|
||||
|
||||
## `pub fn deactivate_relay(&self, relay: &Relay) -> Result<()>`
|
||||
|
||||
- Sets relay status to `inactive`
|
||||
- Logs activity as `(deactivate_relay, relay_id)`
|
||||
|
||||
## `pub fn activate_relay(&self, relay: &Relay) -> Result<()>`
|
||||
|
||||
- Sets relay status to `active`
|
||||
- Logs activity as `(activate_relay, relay_id)`
|
||||
|
||||
## `pub fn fail_relay_sync(&self, relay: &Relay, sync_error: &str) -> Result<()>`
|
||||
|
||||
- Sets relay status to `inactive`, sets `sync_error`
|
||||
- Logs activity as `(fail_relay_sync, relay_id)`
|
||||
|
||||
## `pub fn mark_relay_synced(&self, relay_id: &str) -> Result<()>`
|
||||
|
||||
- Sets `synced = 1`, `status = 'active'`, clears `sync_error`
|
||||
- No activity log (called by infra after successful sync)
|
||||
|
||||
## `pub fn max_activity_at(&self) -> Result<i64>`
|
||||
|
||||
- Returns the maximum `created_at` value from the activity table, or 0 if empty
|
||||
- Used by infra to initialize the since guard on startup
|
||||
|
||||
## `pub fn list_activity(&self, since: &i64) -> Result<Vec<Activity>>`
|
||||
|
||||
- Returns all activity occuring after `since`
|
||||
|
||||
## `pub fn list_activity_for_relay(&self, relay_id: &str) -> Result<Vec<Activity>>`
|
||||
|
||||
- Returns all activity where `resource_type = 'relay'` and `resource_id = relay_id`
|
||||
- Ordered newest-first
|
||||
|
||||
Reference in New Issue
Block a user