Rebase onto upstream v1.6.8, so the upstream segment moves and the aio
patch counter resets to 1.
- #63 merge upstream v1.6.8: ticket waves (per-wave price/currency/
stock/fiat), paginated tickets, the organiser ticket-image template.
Pricing and inventory now follow the wave the buyer selected rather
than the primary-wave roll-up; npub checkout and DM delivery kept
where upstream removed them; `_parse_date` accepts the ISO datetimes
our closing dates carry.
- #63 drop the SatsPay/watchonly on-chain surface — on-chain will go
through native LndRest once aiolabs/lnbits#53 lands (#41).
- #63 publish the active wave over Nostr, with `nostr_published_wave_id`
(fork migration m004) so the reconciliation sweep republishes at wave
boundaries (#61).
- #64 require a capacity on every ticket wave (#34, #62).
`migrations.py` stays byte-identical to upstream. v1.6.8 adds no
upstream migrations over v1.6.1 — waves live in `extra` JSON — so no
`dbversions` surgery is needed on existing installs; only the fork
namespace advances, `events_fork` 3 -> 4.
- #62 `tickets_available` is always published, including zero. Omitting
it used to mean "unlimited", which nothing else agreed with:
api_get_event and api_ticket_create both read `amount_tickets < 1` as
sold out, so a zero-capacity event advertised unlimited tickets on the
card and returned 410 to every purchase. Three such events were live
on aio-demo. The admin form no longer offers 0 either.
No schema change. Existing zero-capacity events are not migrated — we
cannot infer whether the organiser meant unlimited or forgot to set a
number — and they will read as sold out once republished. The #55 sweep
will not flag them on its own, so /republish-all is the way to refresh.
Ticket availability and publish delivery.
- #58 publishes are confirmed against the relay's NIP-01 `OK` instead of
the send queue. A publish that never reaches a relay now leaves the row
flagged for the sweep rather than reporting success and clearing it —
which had silently reverted a completed repair on cfaun. Verified end
to end on aio-demo: delivery confirmed, the no-relay failure caught
with the relay's own diagnostic in the log, and the retry recovering
once the relay returned.
- #59 `amount_tickets` is the remaining count; `api_ticket_create` no
longer subtracts `sold` from it. Every event was locking itself as sold
out at half capacity. 16 of 24 live events on aio-demo were affected,
3 already refusing sales with stock remaining.
No schema change. Affected events start selling again on upgrade; worth
re-running an availability check per host afterwards.
Nostr publish reliability.
- #54 the two paths that skip a NIP-52 publish now log at WARNING
instead of silently (bare return / debug); publish failures moved to
ERROR
- #55 `events.nostr_publish_pending` marks a row from before each
publish attempt until a confirmed success, and a 5-minute sweep
republishes whatever is still flagged, so drift recovers on its own
instead of waiting for an operator who knows to run /republish-all
- #55 the NostrClient send loop holds and retries a dequeued req
(bounded at 3) rather than dropping it
Adds migration m003 (events.nostr_publish_pending). Verified on bohm:
events_fork 1 -> 3, column present, event created and published to a
relay with the flag clearing on success.
Since v1.6.1-aio.14: promo codes enforced — active flag, max_uses with
derived used_count, codes hidden from public event records, anonymous
POST /api/v1/promo/validate/{event_id} preview, single basket_totals
pricing path, Stripe metadata.promo_code (#45).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYwoAkZZmXMMmaBp4WGUBo
The LNbits admin form lagged the webapp's CreateEventDialog:
- Payment methods never rendered. c2d9a96 wired the template to
`paymentMethodOptions` / `acceptsFiat` but never defined them, so the
q-option-group got `options=undefined` and the fiat-currency select
was gated on `undefined`. Rails are now two q-checkboxes; Card is
disabled with an explanatory tooltip when `g.user.fiat_providers` is
empty (same rule as the webapp) and names the providers otherwise.
- Location (NIP-52 `location` tag) and Categories (NIP-52 `t` tags,
same 25-item list as the webapp's category.ts) were missing from the
form even though the model, CRUD and publisher already carry them.
- Datetimes are stamped with the browser's UTC offset on submit, as the
webapp does; `_to_unix` treats naive values as UTC, so 18:00 CEST
entered here went out on Nostr as 18:00 UTC. Table columns render
"YYYY-MM-DD HH:MM" instead of the raw ISO string.
- Validation: title + start date required, end >= start on the folded
date+time, fiat currency required when a sat-priced event accepts
card. Create is enabled once wallet + title + start are set; info,
closing date, tickets and price were all effectively required before
because the disable check compared undefined fields to null.
- Labels follow the payment-rails vocabulary: "Unit" -> "Price
currency", "Fiat checkout currency" -> "Fiat currency"; ticket
closing date and end date explain their defaults.
- A fiat-priced event mirrors `fiat_currency = currency` on save so the
payload and the `tickets_fiat_currency` tag stay coherent.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018b1bDExMX7W3a47wcgFUjb
The v1.6.1-aio.9 mail still scored 8.4/10 on mail-tester: the remaining
deduction was HTML_IMAGE_ONLY (1.8) — an HTML part whose only content of
note is a remote <img>. Remote images are also blocked by default in most
clients until the reader opts in, and a bare QR saved from that mail says
nothing about what it opens.
- New `qr.py` module (QR + logo helpers moved out of views_api) with
`render_ticket_card`: site title, event name, when/where, the branded
QR, name on ticket, ticket id and the door instruction, laid out with
the bundled DejaVu Sans; `format_event_when` gives "Fri 19 Feb 2027,
16:00 - 20:00"; filenames are `ticket-<event-slug>-<id8>.png`.
- `GET /events/api/v1/ticket-card/{ticket_id}` serves the same PNG
(anonymous, like the QR endpoint); the email's "Ticket image" link
now points there.
- The ticket email becomes multipart/mixed: text + HTML alternatives
(URLs as links, no <img>) plus the card as a PNG attachment, which
clients show inline at the end of the message and which works offline
at the door.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYwoAkZZmXMMmaBp4WGUBo
A tester's ticket email landed in spam. A mail-tester run against demo
scored 8.3/10 with SPF, DKIM and DMARC all passing through the VPS relay,
so the deductions were all in the message: MISSING_DATE (1.4),
HTML_IMAGE_ONLY_04 (0.3), MISSING_MID (0.1) — and Gmail/Outlook weigh a
missing Date/Message-ID as "machine-generated" far more than that.
- `build_ticket_email` sets Date, a Message-ID under the sender domain,
and a From display name from `lnbits_site_title`.
- The body now carries the event name, dates, location, name on ticket,
ticket id and the door instruction, so the HTML part is no longer a
QR with a handful of words.
Upstream candidate: lnbits core `send_email` has the same omissions.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYwoAkZZmXMMmaBp4WGUBo
Marks the monotonic created_at fix (#26). aio semver stays ahead of the
upstream 1.6.1 tag per fork versioning rules.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closesaiolabs/events#23. Pre-cascade prerequisite for aiolabs/lnbits#17
(signer abstraction phase 1), which lands an m002 startup job that
NULLs the legacy `accounts.prvkey` column. After this migration, the
events extension reads no plaintext nsec and works with any
NostrSigner backend (LocalSigner / RemoteBunkerSigner / ClientSideOnlySigner).
## What changed
### nostr_hooks.py — publish_or_delete_nostr_event
Was: pulled `(account.pubkey, account.prvkey)` from the wallet owner,
passed both to `publish_event_to_nostr`. Hard-skipped publish when
`account.prvkey` was None.
Now: calls `await resolve_for_wallet(event.wallet)` (the DRY helper
from aiolabs/lnbits#23 — wallet → account → signer → can_sign-check
in one call, returns None on any soft-fail). Passes the resolved
`NostrSigner` to the publisher. Soft-skip on None (wallet missing,
account unclassified, or ClientSideOnlySigner where the server has
no signing authority) — matching previous "no prvkey" behavior.
### nostr_publisher.py — publish_event_to_nostr
Was: accepted `(account_pubkey, account_prvkey)` and signed via a
local `sign_nostr_event` helper that called `coincurve.PrivateKey
.sign_schnorr` directly on the plaintext nsec.
Now: accepts `signer: NostrSigner`. Builds the unsigned event dict
(`kind`/`created_at`/`tags`/`content`), hands it to
`await signer.sign_event(...)`, reconstructs the local `NostrEvent`
model from the signed dict (`id`/`pubkey`/`sig` fields). The signer
backend (LocalSigner / RemoteBunkerSigner) is transparent.
Removed the `sign_nostr_event` helper entirely — the signer abstraction
handles all signing now.
Dropped the `coincurve` import; no direct crypto in this extension.
## Acceptance
- [x] keypair helper replaced (nostr_hooks no longer touches account.prvkey)
- [x] publish_event_to_nostr accepts NostrSigner instead of (pubkey, prvkey)
- [x] extension-local Schnorr code removed (sign_nostr_event gone)
- [x] re-grep `events/`: zero `account.prvkey` references
- [x] version bumped: 1.6.1-aio.3 → 1.6.1-aio.4
Manual smoke testing + tag + catalog entry follow the migration
landing; will run against the regtest stack with lnbits on
`issue-18-phase-2.3` (which validates both LocalSigner and
RemoteBunkerSigner signing paths end-to-end).
## Cross-references
- aiolabs/events#23 — issue this commit closes
- aiolabs/lnbits#17 — the cascading signer-abstraction PR
- aiolabs/lnbits#23 — the resolve_for_wallet helper this uses
- aiolabs/lnbits#26 — phase 2.3 (sign_event over bunker, validated against
aiolabs/nsecbunkerd@fb1c239)
- aiolabs/lnbits#21 — umbrella audit identifying 5 affected extensions
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>