Skip to content

fix(beats): allow Publisher to PATCH any beat description#326

Closed
ThankNIXlater wants to merge 1 commit intoaibtcdev:mainfrom
ThankNIXlater:fix-publisher-beat-patch
Closed

fix(beats): allow Publisher to PATCH any beat description#326
ThankNIXlater wants to merge 1 commit intoaibtcdev:mainfrom
ThankNIXlater:fix-publisher-beat-patch

Conversation

@ThankNIXlater
Copy link
Copy Markdown
Contributor

Summary

Adds publisher override to the ownership check on PATCH /api/beats/:slug. When the authenticated address matches the designated Publisher (from GET /api/config/publisher), the ownership check is bypassed, allowing the Publisher to update any beat's name, description, or color.

Changes

  • Import getConfig from do-client and CONFIG_PUBLISHER_ADDRESS from constants
  • After ownership check fails, query the publisher config
  • If the caller is the Publisher, allow the update to proceed
  • Non-publisher agents can still only update beats they own

Context

This is consistent with how DELETE /api/beats/:slug already works (publisher-only via verifyPublisher()). The route-level fix mirrors the DO-level pattern used in delete.

Closes #317

Adds publisher override to the ownership check on PATCH /api/beats/:slug.
When the authenticated address matches the designated Publisher (from
GET /api/config/publisher), the ownership check is bypassed.

Consistent with how DELETE /api/beats/:slug already works (publisher-only).

Closes aibtcdev#317
Copy link
Copy Markdown
Contributor

@arc0btc arc0btc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adds publisher override to PATCH /api/beats/:slug so the designated Publisher can update any beat's description, consistent with how DELETE already works.

What looks good:

  • The ownership check flow is clean: fail fast on ownership, then check publisher as a fallback — this is the right order and avoids the extra DO call for normal owners
  • publisherConfig?.value === btc_address safely handles null config (undefined !== address → 403) — no security bypass possible
  • Route ordering for /api/beats/membership is correctly placed before /:slug to avoid slug collision — good defensive comment too
  • Input validation on the new membership endpoint matches the existing pattern (bech32 check before DO call)

[question] Mixed scope (src/routes/beats.ts)
The PR title and description only mention the publisher PATCH fix, but the diff also adds a new GET /api/beats/membership endpoint (26 of the 34 added lines). Is this intentional bundling, or did it get included accidentally? The membership endpoint looks correct on its own, but it's undocumented in this PR's context — worth a note in the description so reviewers know what they're getting.

[question] Membership 500 on empty result (src/routes/beats.ts:55-58)
getBeatMembership returns BeatMembershipData | null and the route returns 500 when result is falsy:

if (!result) {
  return c.json({ error: "Failed to fetch beat membership" }, 500);
}

getBeatMembership in do-client.ts returns data.data ?? null — if the DO returns { ok: true, data: null } for a valid address with no memberships, this would 500 instead of returning an empty result. Assuming the DO always returns [] (truthy) for no memberships this is fine, but worth confirming the DO behavior.

[nit] GET /api/beats/membership has no auth requirement — probably correct since beats are public, just confirming it's intentional.

Operational note: We call PATCH /api/beats/:slug in our ordinals-market-data sensors when managing beat state. The publisher check adds one extra DO round-trip per non-owner PATCH attempt, which is a small cost and acceptable for this path.

Copy link
Copy Markdown
Contributor

@tfireubs-ui tfireubs-ui left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Publisher override is safely guarded — null-safe optional chaining on publisherConfig means unconfigured publisher correctly falls through to 403 rather than granting blanket access. The membership endpoint placement before /:slug is correct to avoid slug pattern swallowing. Clean fix for #317.

@ThankNIXlater
Copy link
Copy Markdown
Contributor Author

Friendly ping - this PR is ready for review. Happy to address any feedback. 🙏

@biwasxyz
Copy link
Copy Markdown
Contributor

biwasxyz commented Apr 4, 2026

Code review

No issues found. Checked for bugs, duplicates against main, and overlap with other open PRs. Change is not yet applied in main and does not duplicate another open PR.

whoabuddy pushed a commit that referenced this pull request Apr 8, 2026
Cherry-picked from PR #326. Membership endpoint excluded (separate scope).

Original-PR: #326
Co-Authored-By: Nix <trumptoshi@gmail.com>
whoabuddy added a commit that referenced this pull request Apr 8, 2026
… (#416)

Cherry-picked from PR #326. Membership endpoint excluded (separate scope).

Original-PR: #326

Co-authored-by: Nix <trumptoshi@gmail.com>
@whoabuddy
Copy link
Copy Markdown
Contributor

Core fix shipped in #416 (publisher PATCH override). The bundled GET /api/beats/membership endpoint was excluded — it's useful but belongs in its own issue. Thanks @ThankNIXlater for the contribution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow Publisher to PATCH any beat description

5 participants