feat: support optional user_id ticket identifier

Add an alternative ticket identifier scheme: instead of (name, email),
external integrations can issue tickets bound to an LNbits user_id.

- m007 adds the user_id column on events.ticket
- CreateTicket validator enforces exactly one identifier scheme per ticket
- Ticket / PublicTicket: name, email, user_id all Optional
- _parse_ticket_row reverses the empty-string sentinel used to keep the
  NOT NULL name/email columns satisfied when user_id is the identifier
- POST /tickets/{event_id} dispatches to _create_user_id_ticket vs
  _create_named_ticket based on the supplied identifier
- New GET /tickets/user/{user_id} returns tickets for a given user

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-05-05 18:36:04 +02:00
commit dfabcb8f54
2 changed files with 102 additions and 18 deletions

View file

@ -1,7 +1,7 @@
from datetime import datetime
from fastapi import Query
from pydantic import BaseModel, EmailStr, Field, validator
from pydantic import BaseModel, EmailStr, Field, root_validator, validator
class PromoCode(BaseModel):
@ -94,21 +94,36 @@ class TicketExtra(BaseModel):
class CreateTicket(BaseModel):
name: str
email: EmailStr
name: str | None = None
email: EmailStr | None = None
user_id: str | None = None # LNbits user id (alternative to name+email)
promo_code: str | None = None
refund_address: str | None = None
nostr_identifier: str | None = None
payment_method: str | None = None
fiat_provider: str | None = None
@root_validator
def validate_identifiers(cls, values):
name = values.get("name")
email = values.get("email")
user_id = values.get("user_id")
if not user_id and not (name and email):
raise ValueError(
"Either user_id or both name and email must be provided"
)
if user_id and (name or email):
raise ValueError("Cannot provide both user_id and name/email")
return values
class Ticket(BaseModel):
id: str
wallet: str
event: str
name: str
email: str
name: str | None = None
email: str | None = None
user_id: str | None = None
registered: bool
paid: bool
time: datetime
@ -118,7 +133,7 @@ class Ticket(BaseModel):
class PublicTicket(BaseModel):
event: str
name: str
name: str | None = None
registered: bool
paid: bool
time: datetime