Rebases the aio fork onto upstream v1.6.1 (4bf867e), pulling in:
- fiat checkout + email/Nostr DM ticket notifications (PR #50)
- currency-conversion fix (v1.5.0)
- custom notification subject/body (v1.6.0)
- resend-email button on the ticket list (PR #51)
Notable merges:
- views_api.api_event_update keeps the explicit-field-list gating from
the aio.4 security fix, with allow_fiat + fiat_currency added so an
owner editing a fiat-enabled event keeps the fiat config.
- models.PublicEvent now exposes both upstream's fiat fields and our
location / categories / status fields.
- migrations.py reverts to byte-identical to upstream v1.6.1 (no aio
entries); fork schema lives in migrations_fork.py (per aiolabs/lnbits#8).
- Lint reformatted with black + ruff to match upstream style.
Contributors entry adds `padreug` (aio fork maintainer).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`migrations.py` now matches upstream v1.3.0 exactly. Every aio-only
schema delta (the old m007-m011: user_id, status, nostr_event_id +
created_at, settings table, location + categories) moves into a
single `m001_aio_event_schema` function in `migrations_fork.py`,
tracked under `events_fork` in `dbversions` by the loader added in
aiolabs/lnbits@ae997181.
Idempotency guards on every ADD COLUMN / CREATE TABLE let the
squashed migration no-op cleanly on dev DBs that already ran the
old m007-m011 — schema lands identical from either path.
Why now: aiolabs/lnbits#8. We're about to rebase events onto
upstream v1.6.1 which adds its own m007_add_allow_fiat. With this
move done first, migrations.py stays a fast-forward on rebase and
our fork-only schema lives in a separate file that never collides.
Requires aiolabs/lnbits @ ae997181 or later for the extension_fork
loader. Running on an upstream lnbits without the loader patch
will NOT apply the fork schema — but the aiolabs deploy fleet
already pulls from aiolabs/lnbits, so this is the only host we
ship to.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add GET /events/api/v1/events/settings/public — invoice-key-gated
(anyone with a wallet) — returning just `{ auto_approve }`. The webapp
needs this to render accurate edit-flow copy without forcing every
event creator to also be an LNbits admin.
The admin-only GET /settings stays the source of truth for the full
EventsSettings payload.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The PUT /events/{id} endpoint blindly copied every field from the
request body onto the existing event, including `status`. A non-admin
owner with auto_approve=false could PUT {"status": "approved", ...}
and self-approve, bypassing review entirely.
Replace the blanket setattr loop with an explicit field list (status
omitted) and derive the new status from the same admin / auto_approve
gate that api_event_create uses. Reconcile Nostr against the status
transition:
approved → approved : re-publish the replaceable NIP-52 event
proposed → approved : fresh publish
approved → proposed : NIP-09 delete so the public feed drops it
until the edit is re-approved
proposed → proposed : no-op
Also apply the same end/closing-date defaulting as create_event so an
edit that omits those fields doesn't wipe them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
event_start_date / event_end_date now accept either YYYY-MM-DD (date-only)
or YYYY-MM-DDTHH:MM (ISO datetime). The NIP-52 publisher switches kind
on the "T" delimiter: kind 31922 (date-based, YYYY-MM-DD start/end) when
absent, kind 31923 (time-based, unix-timestamp start/end + day-granularity
D tags) when present. Delete events match the original publish kind.
Closing-date parsing accepts both formats. The LNbits admin form gains
optional HH:MM inputs alongside each date picker; they fold into the
wire-format string on submit and split back on edit.
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>
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>
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>
* feat: register public page saves to localstorage
previsously it fetched all tickets without much information. now it
saves the full scanned ticket after it was scanned, so it can be checked
by some1 without a login
* add last scan
* short id
* prettier
Ran into this issue on my lnbits 1.4 on NixOS using the flake
- Fix m006_add_extra_fields migration that fails on SQLite with syntax error
- Split multi-column ALTER TABLE into separate statements (SQLite doesn't support adding multiple columns in one statement)
* escape name
* add email pydantic validation (API)
* format prettier
* don't allow slash on email also
* make regex const
* use string literals
* make get ticket a POST
* email regex
Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>