feat: NIP-52 Nostr publishing, approval workflow, and event proposals#46
feat: NIP-52 Nostr publishing, approval workflow, and event proposals#46PatMulligan wants to merge 5 commits intolnbits:mainfrom
Conversation
|
@PatMulligan hey! sorry that your PR got totally screwed, but we had this big refactor changing this extension to be loaded dynamically which was done before your pr and took a bit to finish. also the register page, changed, so it now only stores scanned tickets locally and does not fetch them. #43 |
hey no problem at all 😄 I will take a look at your guys work soon to check it out! 🦾 |
|
Thats very understanding of you @PatMulligan. Good luck. Let us know if we can help |
Earlier downstream forks added some of these columns under different migration names. A duplicate-column-tolerant ALTER ADD COLUMN keeps the migration log monotonic for both fresh installs and forks that upgrade in-place. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add an alternative ticket identifier scheme: instead of (name, email),
external integrations can issue tickets bound to an LNbits user_id.
- m007 adds the user_id column on events.ticket
- CreateTicket validator enforces exactly one identifier scheme per ticket
- Ticket / PublicTicket: name, email, user_id all Optional
- _parse_ticket_row reverses the empty-string sentinel used to keep the
NOT NULL name/email columns satisfied when user_id is the identifier
- POST /tickets/{event_id} dispatches to _create_user_id_ticket vs
_create_named_ticket based on the supplied identifier
- New GET /tickets/user/{user_id} returns tickets for a given user
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Non-admin event submissions now land in a "proposed" queue that LNbits
admins review before the event becomes ticketable and publicly listed.
- m008 adds events.events.status (proposed/approved/rejected); m010 seeds
an events.settings singleton row with the auto_approve toggle.
- Models: Event/CreateEvent.status, EventsSettings, optional date fields
with sensible defaults (closing_date defaults to event_end_date which
defaults to event_start_date), PublicEvent.status surfaces the workflow
state on the public endpoint.
- crud: get_all/public/pending_events for the admin views; get/update_settings
for the auto_approve toggle; create_event auto-fills missing date defaults.
- views_api:
* POST /api/v1/events accepts wallet invoice keys so anyone can submit;
handler stamps status="proposed" for non-admins when auto_approve is off
* /public, /all, /pending, /settings (GET+PUT), /{id}/{approve,reject},
/{id}/tickets endpoints; literal-prefix routes declared before /{event_id}
so FastAPI matches them correctly
* Public GET /{event_id} bypasses sold-out / closing-window gates for
proposed/rejected events and returns the trimmed PublicEvent so the SFC
can render a "pending approval" banner
* POST /tickets/{event_id} rejects when event.status != "approved"
- Frontend: index.vue gains an admin Settings card, Pending Approvals list,
status badge column and approve/reject row actions, plus an All Users'
Events admin table; index.js gains the data + methods + an isAdmin probe
via GET /events/all; display.vue shows pending/rejected banners and
hides the Buy Ticket form unless status === "approved".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Approved events are mirrored to Nostr as NIP-52 calendar events (kind
31922) signed by the wallet owner's pubkey, and incoming kind 31922/31923
events from subscribed relays are synced into the local DB so events
created on other LNbits instances or Nostr clients show up locally.
- m009 stores nostr_event_id + nostr_event_created_at on each event
(used for replaceable updates and NIP-09 deletes); m011 adds location
+ JSON-encoded categories list (NIP-52 location/`t` tags).
- models: Event/PublicEvent/CreateEvent gain location, categories,
nostr_event_id, nostr_event_created_at; parse_categories validator
decodes the JSON column on read.
- nostr/{event,nostr_client}.py: Schnorr signing, websocket relay client,
and a NostrEvent model (publish-only and subscribe variants).
- nostr_publisher.py: build/sign NIP-52 kind 31922 events and NIP-09
delete events; publish via the relay client.
- nostr_sync.py: subscribe to kinds 31922/31923, dedupe by nostr_event_id
/ d-tag, upsert Events; auto-approves discovered Nostr events since
they're already public.
- nostr_hooks.py: thin bridge that views_api handlers call to publish
or delete a NIP-52 event for a given local event. Lives in its own
module to keep `from . import nostr_client` out of the view layer
and avoid the views_api -> publisher import cycle.
- views_api: hooks publish_or_delete_nostr_event into create-on-approved,
update-when-already-published, cancel (delete), delete (delete), and
approve (publish).
- __init__.py: 3-task lifespan — wait_for_paid_invoices (upstream),
NostrClient bootstrap, and the NIP-52 sync loop. Module-level
nostr_client global is set by the bootstrap and read dynamically by
publish_or_delete_nostr_event so the import order works regardless of
whether nostrclient is up at startup.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- black/prettier reformatting across new aio code - type annotations on db.fetchone/fetchall callsites in crud.py - explicit dict[str, list[str]] for tag_lists in nostr_sync.py - type:ignore[attr-defined] on Account.prvkey access — the field is added by the aio-fork lnbits.core.models.Account; upstream lnbits does not yet have it, so consumers without the fork must add a prvkey column to accounts before the Nostr publisher can sign. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
it has been le rebased 👌 |
Summary
This PR adds three main features to the events extension, plus several smaller improvements that emerged along the way:
1. Event proposal & approval workflow
statusfield on Event (proposed/approved/rejected)proposedand require LNbits admin approvalauto_approvesetting (e.g. for trusted/demo deployments)2. NIP-52 calendar event publishing
title,start,end,image,location,t(categories),d(event id)3. Bidirectional Nostr sync
Misc improvements
event_end_date,closing_date,infooptional with sensible defaultslocation,categories/events/alland/events/pendingendpoints (usescheck_admin)POST /eventsandPOST /events/proposeinto a single endpointDependencies
nostrclientextension must be installed for Nostr sync; if not, the extension still functions normally without Nostr publishing.prvkeyfield (we have this in our LNbits fork). (The Nostr publishing in nostr_hooks.py reads account.prvkey to sign NIP-52 calendar events with the wallet owner's keypair.)* Footnote:
There is a bit of a misleading feature included here... In one respect I have an admin approval feature, however there is nothing stopping someone from just publishing a NIP-52 to the relay. In the context of a community, running this as a service and/or reducing spam, I wonder if I might also sign published events as the admin to indicate "approved" events versus any old nip-52.
Other note
Currently there's no nostr component of purchasing tickets like you can do with nostrmarket, but it's planned