From ced6ca2b2b7591788424ea990470405dc4275fbc Mon Sep 17 00:00:00 2001 From: Padreug Date: Sat, 23 May 2026 21:02:36 +0200 Subject: [PATCH] feat: organizer-side "Republish mine" button + scoped endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The admin /republish-all hits every approved event regardless of owner — useful for the catalog migration, but heavy. Organizers who want to re-emit just THEIR own events (e.g. after the AIO publisher gained the tickets_* tags and an organizer's events should pick them up) need a lighter knob. Backend: new POST /republish-mine wallet-scoped via require_admin_key, mirrors api_tickets's `all_wallets=true` shape so the page can re-emit across every wallet the user owns. Filters to approved + non-canceled rows. UI: "Republish mine" button alongside "New Event" so every logged-in user sees it (no isAdmin gate). Loading state + confirm dialog + success count notification. Co-Authored-By: Claude Opus 4.7 (1M context) --- static/js/index.js | 31 +++++++++++++++++++++++++++++++ static/js/index.vue | 20 +++++++++++++++++--- views_api.py | 31 +++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 3 deletions(-) diff --git a/static/js/index.js b/static/js/index.js index 8ec41a6..a65f5f8 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -10,6 +10,7 @@ window.PageEvents = { allUserEvents: [], isAdmin: false, republishing: false, + republishingMine: false, settings: { auto_approve: false }, @@ -303,6 +304,36 @@ window.PageEvents = { }) }) }, + republishMyEvents() { + LNbits.utils + .confirmDialog( + 'Re-emit your approved events to Nostr relays?' + ) + .onOk(() => { + this.republishingMine = true + LNbits.api + .request( + 'POST', + '/events/api/v1/events/republish-mine?all_wallets=true', + this.g.user.wallets[0].adminkey + ) + .then(response => { + Quasar.Notify.create({ + type: 'positive', + message: + 'Republished ' + + response.data.republished + + ' of your ' + + response.data.total + + ' events' + }) + }) + .catch(LNbits.utils.notifyApiError) + .finally(() => { + this.republishingMine = false + }) + }) + }, foldDateTime(day, time) { // Combine separate date/time inputs into the wire format // expected by the events extension: "YYYY-MM-DD" or diff --git a/static/js/index.vue b/static/js/index.vue index befc230..4760c6b 100644 --- a/static/js/index.vue +++ b/static/js/index.vue @@ -42,9 +42,23 @@ - New Event +
+ New Event + +
+
+ Re-emit your approved events to Nostr relays. Useful after + a publisher upgrade or if a relay dropped your events. +
diff --git a/views_api.py b/views_api.py index b7c6a0d..35da0cd 100644 --- a/views_api.py +++ b/views_api.py @@ -136,6 +136,37 @@ async def api_republish_all( return {"republished": len(approved), "total": len(events)} +@events_api_router.post("/republish-mine") +async def api_republish_mine( + all_wallets: bool = Query(False), + key_info: WalletTypeInfo = Depends(require_admin_key), +) -> dict: + """Force-republish the caller's own approved events to Nostr relays. + + Same shape as /republish-all but scoped to events owned by the + authenticated wallet (or all wallets belonging to the wallet's + user when `?all_wallets=true`). Lets the organizer trigger the + same migration the admin uses, without needing instance-admin + rights — useful when the AIO publisher gains a new tag set and + an organizer wants their published events to carry it. + + Only events with `status == "approved"` are republished; pending + and rejected rows aren't on relays in the first place, so a + republish for them would be a no-op (or worse, surface a + proposed-but-not-approved row to subscribers). + """ + wallet_ids: list[str] = [key_info.wallet.id] + if all_wallets: + user = await get_user(key_info.wallet.user) + wallet_ids = user.wallet_ids if user else [] + + events = await get_events(wallet_ids) + approved = [e for e in events if e.status == "approved" and not e.canceled] + for event in approved: + await publish_or_delete_nostr_event(event) + return {"republished": len(approved), "total": len(events)} + + @events_api_router.get("/settings") async def api_get_settings( admin: Account = Depends(check_admin),