fix: enforce relay member capacity limits from plan definitions #43

Merged
hodlbod merged 1 commits from userAdityaa/caravel:enforce-member into master 2026-04-22 20:56:04 +00:00
Contributor

Problem

The three pricing plans define member limits (free = 10, basic = 100, growth = unlimited) in Query::list_plans() and the UI displays them in RelayDetailCard, but neither the API nor any backend handler validated member count against these limits. A user on a free plan could admit an unlimited number of members.

Solution

Per maintainer guidance, added a GET /relays/:id/members endpoint that proxies the member list from zooid and reused that same mechanism to enforce limits on plan changes.

/relays/:id/members Endpoint Explained

The GET /relays/:id/members endpoint retrieves the current member list for a relay.

How it works

  1. Authentication & Authorization
    • Requires NIP-98 auth head
    • Only the relay owner (tenant) or an admin can access it
  2. Member Lookup
    • If the relay hasn't been synced to zooid yet (relay.synced == 0), it returns an empty member list immediately (safe default for a relay not yet live)
    • If the relay is synced, it makes a signed request to zooid (GET /relay/:id/members) using NIP-98 auth and parses the response
  3. Response
{
  "data": {
    "members": ["npub1...", "npub2...", ...]
  },
  "code": "ok"
}
Screenshot 2026-04-22 at 5.31.20 PM.png

closes #33

### Problem The three pricing plans define member limits (free = 10, basic = 100, growth = unlimited) in Query::list_plans() and the UI displays them in RelayDetailCard, but neither the API nor any backend handler validated member count against these limits. A user on a free plan could admit an unlimited number of members. ### Solution Per maintainer guidance, added a `GET /relays/:id/members` endpoint that proxies the member list from zooid and reused that same mechanism to enforce limits on plan changes. ### `/relays/:id/members Endpoint Explained` The `GET /relays/:id/members endpoint` retrieves the current member list for a relay. **How it works** 1. Authentication & Authorization * Requires NIP-98 auth head * Only the relay owner (tenant) or an admin can access it 2. Member Lookup * If the relay hasn't been synced to zooid yet (relay.synced == 0), it returns an empty member list immediately (safe default for a relay not yet live) * If the relay is synced, it makes a signed request to zooid (GET /relay/:id/members) using NIP-98 auth and parses the response 3. Response ```json { "data": { "members": ["npub1...", "npub2...", ...] }, "code": "ok" } ``` <img width="917" alt="Screenshot 2026-04-22 at 5.31.20 PM.png" src="attachments/08f3e580-5f46-40e2-98de-f986d64cd62d"> closes #33
hodlbod reviewed 2026-04-22 16:39:30 +00:00
@@ -99,0 +102,4 @@
}
if self.api_secret.trim().is_empty() {
anyhow::bail!("missing ZOOID_API_SECRET");
}
Owner

We should validate this in the constructor

We should validate this in the constructor
hodlbod marked this conversation as resolved
@@ -99,0 +106,4 @@
let client = reqwest::Client::new();
let base = self.api_url.trim_end_matches('/');
let url = format!("{base}/relay/{relay_id}/members");
Owner

Is this endpoint implemented in zooid or does this depend on a PR to add it?

Is this endpoint implemented in zooid or does this depend on a PR to add it?
Author
Contributor

I just wanted you to confirm that this flow is correct, this change depends on the PR being merged into Zooid first.

I’ll push the endpoints PR to Zooid first, then follow up with the final push for this one.

I just wanted you to confirm that this flow is correct, this change depends on the PR being merged into Zooid first. > I’ll push the endpoints PR to Zooid first, then follow up with the final push for this one.
hodlbod marked this conversation as resolved
hodlbod added 1 commit 2026-04-22 20:55:59 +00:00
hodlbod force-pushed enforce-member from d1fe9ff61c to e17c7481be 2026-04-22 20:55:59 +00:00 Compare
hodlbod merged commit c261d8a146 into master 2026-04-22 20:56:04 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: coracle/caravel#43