chore: rebase onto upstream v1.6.1 + bump to v1.6.1-aio.1
Some checks failed
lint.yml / chore: rebase onto upstream v1.6.1 + bump to v1.6.1-aio.1 (push) Failing after 0s

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>
This commit is contained in:
Padreug 2026-05-22 09:24:35 +02:00
commit 27cc8d2f1c
7 changed files with 22 additions and 34 deletions

View file

@ -75,9 +75,7 @@ def events_start():
except Exception as exc: except Exception as exc:
logger.error(f"[EVENTS] Nostr sync task failed: {exc}") logger.error(f"[EVENTS] Nostr sync task failed: {exc}")
task3 = create_permanent_unique_task( task3 = create_permanent_unique_task("ext_events_nostr_sync", _sync_nostr_events)
"ext_events_nostr_sync", _sync_nostr_events
)
scheduled_tasks.append(task3) scheduled_tasks.append(task3)

View file

@ -1,8 +1,8 @@
{ {
"id": "events", "id": "events",
"version": "1.6.1", "version": "1.6.1-aio.1",
"name": "Events", "name": "Events",
"repo": "https://github.com/lnbits/events", "repo": "https://git.atitlan.io/aiolabs/events",
"short_description": "Sell and register event tickets", "short_description": "Sell and register event tickets",
"description": "", "description": "",
"tile": "/events/static/image/events.png", "tile": "/events/static/image/events.png",
@ -32,6 +32,11 @@
"name": "motorina0", "name": "motorina0",
"uri": "https://github.com/motorina0", "uri": "https://github.com/motorina0",
"role": "Developer" "role": "Developer"
},
{
"name": "padreug",
"uri": "https://git.atitlan.io/padreug",
"role": "Developer (aio fork: approval workflow + NIP-52 Nostr sync + edit gating)"
} }
], ],
"images": [ "images": [

View file

@ -140,9 +140,7 @@ class CreateTicket(BaseModel):
email = values.get("email") email = values.get("email")
user_id = values.get("user_id") user_id = values.get("user_id")
if not user_id and not (name and email): if not user_id and not (name and email):
raise ValueError( raise ValueError("Either user_id or both name and email must be provided")
"Either user_id or both name and email must be provided"
)
if user_id and (name or email): if user_id and (name or email):
raise ValueError("Cannot provide both user_id and name/email") raise ValueError("Cannot provide both user_id and name/email")
return values return values

View file

@ -39,8 +39,7 @@ class NostrClient:
async def connect(self) -> WebSocketApp: async def connect(self) -> WebSocketApp:
relay_endpoint = encrypt_internal_message("relay", urlsafe=True) relay_endpoint = encrypt_internal_message("relay", urlsafe=True)
ws_url = ( ws_url = (
f"ws://localhost:{settings.port}" f"ws://localhost:{settings.port}" f"/nostrclient/api/v1/{relay_endpoint}"
f"/nostrclient/api/v1/{relay_endpoint}"
) )
logger.info("[EVENTS] Connecting to nostrclient WebSocket...") logger.info("[EVENTS] Connecting to nostrclient WebSocket...")
@ -58,12 +57,8 @@ class NostrClient:
logger.warning(f"[EVENTS] WebSocket error: {error}") logger.warning(f"[EVENTS] WebSocket error: {error}")
def on_close(_, status_code, message): def on_close(_, status_code, message):
logger.warning( logger.warning(f"[EVENTS] WebSocket closed: {status_code} {message}")
f"[EVENTS] WebSocket closed: {status_code} {message}" self.receive_event_queue.put_nowait(ValueError("WebSocket closed"))
)
self.receive_event_queue.put_nowait(
ValueError("WebSocket closed")
)
ws = WebSocketApp( ws = WebSocketApp(
ws_url, ws_url,
@ -118,9 +113,7 @@ class NostrClient:
async def subscribe(self, filters: list[dict]): async def subscribe(self, filters: list[dict]):
"""Subscribe to events matching the given filters.""" """Subscribe to events matching the given filters."""
self.subscription_id = "events-" + urlsafe_short_hash()[:32] self.subscription_id = "events-" + urlsafe_short_hash()[:32]
await self.send_req_queue.put( await self.send_req_queue.put(["REQ", self.subscription_id, *filters])
["REQ", self.subscription_id, *filters]
)
logger.info( logger.info(
f"[EVENTS] Subscribed to NIP-52 events " f"[EVENTS] Subscribed to NIP-52 events "
f"(sub: {self.subscription_id[:20]}...)" f"(sub: {self.subscription_id[:20]}...)"

View file

@ -78,7 +78,7 @@ def build_nip52_event(event: Event, pubkey: str) -> NostrEvent:
tags.append(["image", event.banner]) tags.append(["image", event.banner])
if event.location: if event.location:
tags.append(["location", event.location]) tags.append(["location", event.location])
for cat in (event.categories or []): for cat in event.categories or []:
tags.append(["t", cat]) tags.append(["t", cat])
nostr_event = NostrEvent( nostr_event = NostrEvent(

View file

@ -137,9 +137,11 @@ async def wait_for_nostr_events(nostr_client: NostrClient):
while True: while True:
try: try:
# Subscribe to NIP-52 calendar events # Subscribe to NIP-52 calendar events
await nostr_client.subscribe([ await nostr_client.subscribe(
[
{"kinds": [31922, 31923]}, {"kinds": [31922, 31923]},
]) ]
)
# Process incoming events # Process incoming events
while True: while True:

View file

@ -161,9 +161,7 @@ async def api_get_event(event_id: str) -> Event:
# closing_date is filled in by create_event (defaults to end_date or # closing_date is filled in by create_event (defaults to end_date or
# start_date) but the field is typed Optional, so guard for the typechecker. # start_date) but the field is typed Optional, so guard for the typechecker.
closing_date = ( closing_date = event.closing_date or event.event_end_date or event.event_start_date
event.closing_date or event.event_end_date or event.event_start_date
)
# Accept either YYYY-MM-DD or full ISO 8601 datetime (event_end_date # Accept either YYYY-MM-DD or full ISO 8601 datetime (event_end_date
# may carry a time component since v1.3.0-aio.3 / our start-end-time # may carry a time component since v1.3.0-aio.3 / our start-end-time
# feature). # feature).
@ -210,10 +208,7 @@ async def api_event_create(
ext_settings = await get_settings() ext_settings = await get_settings()
user_id = wallet.wallet.user user_id = wallet.wallet.user
is_admin = ( is_admin = user_id == settings.super_user or user_id in settings.lnbits_admin_users
user_id == settings.super_user
or user_id in settings.lnbits_admin_users
)
if not is_admin and not ext_settings.auto_approve: if not is_admin and not ext_settings.auto_approve:
data.status = "proposed" data.status = "proposed"
@ -249,9 +244,7 @@ async def api_event_update(
status_code=HTTPStatus.NOT_FOUND, detail="Event does not exist." status_code=HTTPStatus.NOT_FOUND, detail="Event does not exist."
) )
if event.wallet != wallet.wallet.id: if event.wallet != wallet.wallet.id:
raise HTTPException( raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your event.")
status_code=HTTPStatus.FORBIDDEN, detail="Not your event."
)
from lnbits.settings import settings from lnbits.settings import settings
@ -292,7 +285,6 @@ async def api_event_update(
event.status = "approved" if (is_admin or ext_settings.auto_approve) else "proposed" event.status = "approved" if (is_admin or ext_settings.auto_approve) else "proposed"
event = await update_event(event) event = await update_event(event)
if event.status == "approved": if event.status == "approved":