diff --git a/nostr_publisher.py b/nostr_publisher.py index 6867041..a6d487b 100644 --- a/nostr_publisher.py +++ b/nostr_publisher.py @@ -39,25 +39,12 @@ def build_nip52_event(event: Event, pubkey: str) -> NostrEvent: Time-based (kind 31923) if event_start_date carries an HH:MM, otherwise date-based (kind 31922). Tags: - d - event.id - title - event.name - start - unix timestamp (31923) or YYYY-MM-DD (31922) - end - same encoding (optional) + d - event.id + title - event.name + start - unix timestamp (31923) or YYYY-MM-DD (31922) + end - same encoding (optional) image, location, t (categories) - optional - tickets_available - current remaining capacity (omitted when unlimited) - tickets_sold - running paid-count (always emitted; clients can - derive original_capacity = available + sold) - tickets_price - price_per_ticket (always emitted; 0 means free) - tickets_currency - the currency string - tickets_allow_fiat - "true" when fiat checkout is enabled (omitted otherwise) - tickets_fiat_currency - the fiat settle currency (only when allow_fiat) Content: event.info - - The four ticket_* tags are AIO custom additions outside the NIP-52 - spec; spec-compliant clients ignore unknown tags so this stays - backwards-compatible. They let connected clients render the - "X tickets remaining" badge and the Buy CTA without an extra REST hop, - and pick up live inventory updates via the same relay subscription. """ time_based = _has_time(event.event_start_date) kind = 31923 if time_based else 31922 @@ -94,21 +81,6 @@ def build_nip52_event(event: Event, pubkey: str) -> NostrEvent: for cat in event.categories or []: tags.append(["t", cat]) - # `amount_tickets == 0` means unlimited capacity in this extension's - # schema. Omitting the tag is how clients distinguish unlimited from - # "0 left" (sold out). - if event.amount_tickets > 0: - tags.append(["tickets_available", str(event.amount_tickets)]) - tags.append(["tickets_sold", str(event.sold)]) - tags.append(["tickets_price", str(event.price_per_ticket)]) - tags.append(["tickets_currency", event.currency]) - # Fiat-checkout config — only emitted when allow_fiat is on so - # clients can branch the buy UI without re-reading the schema. - if event.allow_fiat: - tags.append(["tickets_allow_fiat", "true"]) - if event.fiat_currency: - tags.append(["tickets_fiat_currency", event.fiat_currency]) - nostr_event = NostrEvent( pubkey=pubkey, created_at=int(time.time()), diff --git a/services.py b/services.py index 0a2de28..159bbdc 100644 --- a/services.py +++ b/services.py @@ -1,6 +1,5 @@ from __future__ import annotations -import asyncio from asyncio.tasks import create_task from lnbits.core.models.users import UserNotifications @@ -22,7 +21,6 @@ from .crud import ( update_ticket, ) from .models import Event, Ticket -from .nostr_hooks import publish_or_delete_nostr_event DEFAULT_NOSTR_RELAYS = [ "wss://relay.damus.io", @@ -30,42 +28,19 @@ DEFAULT_NOSTR_RELAYS = [ "wss://relay.nostr.band", ] -# Per-event lock: serializes the counter-update + Nostr republish for a -# single event_id so two paid invoices landing on the listener queue back- -# to-back can't reorder the published state. Lazy-populated; entries are -# left in memory for the lifetime of the process (cheap — one asyncio.Lock -# object per event ever sold). -_event_paid_locks: dict[str, asyncio.Lock] = {} - - -def _event_paid_lock(event_id: str) -> asyncio.Lock: - lock = _event_paid_locks.get(event_id) - if lock is None: - lock = asyncio.Lock() - _event_paid_locks[event_id] = lock - return lock - async def set_ticket_paid(ticket: Ticket) -> Ticket: if ticket.paid: return ticket - async with _event_paid_lock(ticket.event): - ticket.paid = True - await update_ticket(ticket) + ticket.paid = True + await update_ticket(ticket) - event = await get_event(ticket.event) - assert event, "Couldn't get event from ticket being paid" - event.sold += 1 - event.amount_tickets -= 1 - await update_event(event) - - # Republish the NIP-52 calendar event so connected clients see - # the new tickets_available / tickets_sold counters via their - # existing relay subscription. Failures are logged + swallowed - # inside publish_or_delete_nostr_event so a Nostr outage doesn't - # break the payment flow. - await publish_or_delete_nostr_event(event) + event = await get_event(ticket.event) + assert event, "Couldn't get event from ticket being paid" + event.sold += 1 + event.amount_tickets -= 1 + await update_event(event) return ticket diff --git a/views_api.py b/views_api.py index 7fd3aa0..08e94ab 100644 --- a/views_api.py +++ b/views_api.py @@ -14,12 +14,11 @@ from fastapi import ( ) from lnbits.core.crud import get_user from lnbits.core.crud.wallets import get_wallet -from lnbits.core.models import Account, User, WalletTypeInfo +from lnbits.core.models import Account, WalletTypeInfo from lnbits.core.models.payments import CreateInvoice from lnbits.core.services import create_payment_request from lnbits.decorators import ( check_admin, - check_user_exists, require_admin_key, require_invoice_key, ) @@ -46,7 +45,6 @@ from .crud import ( get_settings, get_ticket, get_tickets, - get_tickets_by_user_id, purge_unpaid_tickets, update_event, update_settings, @@ -401,27 +399,6 @@ async def api_tickets( return await get_tickets(wallet_ids) -@tickets_api_router.get("/user/{user_id}") -async def api_tickets_by_user( - user_id: str, - user: User = Depends(check_user_exists), -) -> list[Ticket]: - """All tickets for the authenticated user. - - The `user_id` path param must match the token-bound user so a - Bearer-authenticated session can only enumerate its own tickets. - Returns full `Ticket` rows (not `PublicTicket`) since the owner - needs the payment_hash to render the QR + the `extra` envelope - to surface payment/refund state in My Tickets. - """ - if user_id != user.id: - raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, - detail="Can only fetch your own tickets.", - ) - return await get_tickets_by_user_id(user_id) - - @tickets_api_router.get("/{ticket_id}", response_model=PublicTicket) async def api_get_ticket(ticket_id: str) -> Ticket: ticket = await get_ticket(ticket_id)