feat(nostr): publish the active ticket wave, not the roll-up
Some checks failed
lint.yml / feat(nostr): publish the active ticket wave, not the roll-up (pull_request) Failing after 0s
Some checks failed
lint.yml / feat(nostr): publish the active ticket wave, not the roll-up (pull_request) Failing after 0s
Since upstream v1.6.8 price, currency and inventory belong to time-boxed ticket waves, and the event-level fields `sync_event_ticket_waves` derives are the PRIMARY wave's price/currency and the SUM of every wave's stock. The NIP-52 publisher read those, so as soon as an organiser created a second wave the public card would advertise the early-bird price after early bird closed and count stock in waves that had not opened. Refs #61. `build_nip52_event` now describes the wave a buyer can actually buy from: - several waves can be open at once, and a publisher has no one to ask which one the buyer wants (the purchase endpoint errors with "Please select a ticket wave"), so it advertises the CHEAPEST open wave — the price a buyer is able to obtain. Deviation recorded in docs/upstream-candidates.md. - with no open wave, `tickets_available` is 0 and never omitted: omission used to mean "unlimited", which #34/#62 removed as a concept. - `tickets_payment_methods` is scoped to the advertised wave too. It was derived from `event.allow_fiat` — the primary wave's — so it could offer a fiat rail while `tickets_allow_fiat` was absent and the purchase endpoint would refuse it. They are the same fact and now come from the same place. Wave boundaries are time-driven, and every republish we have is sale-driven, so nothing fires when early bird ends at midnight. Rather than add a scheduler, a publish records which wave it advertised (`nostr_published_wave_id`, m004) and the reconciliation sweep compares that against the wave that would be advertised now, setting `nostr_publish_pending` on a mismatch — reusing the existing retry path. NULL means "never published", which the sweep leaves alone so an upgrade does not republish the whole table on first boot. The selection rule lives in `models.advertised_ticket_wave` so the publisher and the drift detector cannot disagree about what is on the relay. 17 new tests; 114 pass. ruff, black, prettier clean; mypy error set still identical to HEAD's baseline.
This commit is contained in:
parent
d90a0f8322
commit
15c2276e57
10 changed files with 525 additions and 39 deletions
43
crud.py
43
crud.py
|
|
@ -12,6 +12,7 @@ from .models import (
|
|||
Ticket,
|
||||
TicketExtra,
|
||||
TicketFilters,
|
||||
advertised_wave_key,
|
||||
sync_event_ticket_waves,
|
||||
)
|
||||
|
||||
|
|
@ -298,6 +299,48 @@ async def get_pending_events() -> list[Event]:
|
|||
return [cast(Event, sync_event_ticket_waves(e)) for e in events]
|
||||
|
||||
|
||||
async def flag_wave_transitions() -> int:
|
||||
"""Flag events whose advertised ticket wave has moved on.
|
||||
|
||||
Every republish this extension performs is *sale*-driven. A wave
|
||||
boundary is a *date* boundary, so when early bird closes at midnight
|
||||
nothing fires and the relay keeps serving the closed wave's price until
|
||||
the next ticket happens to sell (aiolabs/events#61).
|
||||
|
||||
Rather than add a scheduler and a second publish path, this compares the
|
||||
wave a row would advertise now against the one its last successful
|
||||
publish did (`nostr_published_wave_id`) and sets `nostr_publish_pending`
|
||||
on a mismatch — handing the work to the existing reconciliation sweep,
|
||||
which already retries, survives restarts and logs.
|
||||
|
||||
Rows with NULL `nostr_published_wave_id` are skipped: that means "never
|
||||
published, or published before the column existed", which is no evidence
|
||||
of drift. Flagging them would republish the whole table on first boot
|
||||
after the upgrade.
|
||||
|
||||
Returns the number of rows newly flagged.
|
||||
"""
|
||||
events = await db.fetchall(
|
||||
"""
|
||||
SELECT * FROM events.events
|
||||
WHERE nostr_published_wave_id IS NOT NULL
|
||||
AND nostr_publish_pending = FALSE
|
||||
AND canceled = FALSE
|
||||
AND status = 'approved'
|
||||
""",
|
||||
model=Event,
|
||||
)
|
||||
flagged = 0
|
||||
for event in events:
|
||||
event = cast(Event, sync_event_ticket_waves(event))
|
||||
if advertised_wave_key(event) == event.nostr_published_wave_id:
|
||||
continue
|
||||
event.nostr_publish_pending = True
|
||||
await update_event(event)
|
||||
flagged += 1
|
||||
return flagged
|
||||
|
||||
|
||||
async def get_events_pending_republish() -> list[Event]:
|
||||
"""Events whose relay copy may be behind this row.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue