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>