diff --git a/crud.py b/crud.py index 8abcbbb..e92e47e 100644 --- a/crud.py +++ b/crud.py @@ -1,4 +1,3 @@ -import json from datetime import datetime, timedelta, timezone from typing import Optional @@ -7,30 +6,6 @@ from lnbits.helpers import urlsafe_short_hash from .models import CreateEvent, Event, Ticket, TicketExtra - -def _parse_ticket_row(row) -> dict: - """ - Parse a database row into a dict suitable for Ticket model creation. - Handles: - - Empty string to None conversion for name/email - - JSON string to dict conversion for extra field - """ - ticket_data = dict(row) - - # Convert empty strings back to None for the model - if ticket_data.get("name") == "": - ticket_data["name"] = None - if ticket_data.get("email") == "": - ticket_data["email"] = None - - # Parse extra field from JSON string if needed - # (db.insert() serializes to JSON, but manual fetchone/fetchall returns string) - extra = ticket_data.get("extra") - if isinstance(extra, str): - ticket_data["extra"] = json.loads(extra) - - return ticket_data - db = Database("ext_events") @@ -45,12 +20,7 @@ async def create_ticket( ) -> Ticket: now = datetime.now(timezone.utc) - # TODO: Check if this empty string workaround is still needed. - # This converts None to empty strings for database storage because: - # 1. Database may have NOT NULL constraints on name/email columns - # 2. When user_id is provided, name/email are not used (mutually exclusive) - # 3. The get_ticket() functions convert empty strings back to None when reading - # Consider using nullable columns instead of this empty string pattern. + # Handle database constraints: if user_id is provided, use empty strings for name/email if user_id: db_name = "" db_email = "" @@ -58,28 +28,7 @@ async def create_ticket( db_name = name or "" db_email = email or "" - # Create ticket with database-compatible values for insertion - # Using db.insert() ensures proper serialization of the extra field (TicketExtra) - # across all database backends (SQLite, PostgreSQL, CockroachDB) - db_ticket = Ticket( - id=payment_hash, - wallet=wallet, - event=event, - name=db_name, - email=db_email, - user_id=user_id, - registered=False, - paid=False, - reg_timestamp=now, - time=now, - extra=TicketExtra(**extra) if extra else TicketExtra(), - ) - - await db.insert("events.ticket", db_ticket) - - # Return ticket with original name/email values (not empty strings) - # This maintains consistency with how get_ticket() converts empty strings back to None - return Ticket( + ticket = Ticket( id=payment_hash, wallet=wallet, event=event, @@ -93,6 +42,20 @@ async def create_ticket( extra=TicketExtra(**extra) if extra else TicketExtra(), ) + # Create a dict for database insertion with proper handling of constraints + ticket_dict = ticket.dict() + ticket_dict["name"] = db_name + ticket_dict["email"] = db_email + + await db.execute( + """ + INSERT INTO events.ticket (id, wallet, event, name, email, user_id, registered, paid, time, reg_timestamp, extra) + VALUES (:id, :wallet, :event, :name, :email, :user_id, :registered, :paid, :time, :reg_timestamp, :extra) + """, + ticket_dict + ) + return ticket + async def update_ticket(ticket: Ticket) -> Ticket: # Create a new Ticket object with corrected values for database constraints @@ -119,7 +82,14 @@ async def get_ticket(payment_hash: str) -> Optional[Ticket]: if not row: return None - return Ticket(**_parse_ticket_row(row)) + # Convert empty strings back to None for the model + ticket_data = dict(row) + if ticket_data.get("name") == "": + ticket_data["name"] = None + if ticket_data.get("email") == "": + ticket_data["email"] = None + + return Ticket(**ticket_data) async def get_tickets(wallet_ids: str | list[str]) -> list[Ticket]: @@ -128,7 +98,17 @@ async def get_tickets(wallet_ids: str | list[str]) -> list[Ticket]: q = ",".join([f"'{wallet_id}'" for wallet_id in wallet_ids]) rows = await db.fetchall(f"SELECT * FROM events.ticket WHERE wallet IN ({q})") - return [Ticket(**_parse_ticket_row(row)) for row in rows] + tickets = [] + for row in rows: + # Convert empty strings back to None for the model + ticket_data = dict(row) + if ticket_data.get("name") == "": + ticket_data["name"] = None + if ticket_data.get("email") == "": + ticket_data["email"] = None + tickets.append(Ticket(**ticket_data)) + + return tickets async def get_tickets_by_user_id(user_id: str) -> list[Ticket]: @@ -138,7 +118,17 @@ async def get_tickets_by_user_id(user_id: str) -> list[Ticket]: {"user_id": user_id} ) - return [Ticket(**_parse_ticket_row(row)) for row in rows] + tickets = [] + for row in rows: + # Convert empty strings back to None for the model + ticket_data = dict(row) + if ticket_data.get("name") == "": + ticket_data["name"] = None + if ticket_data.get("email") == "": + ticket_data["email"] = None + tickets.append(Ticket(**ticket_data)) + + return tickets async def delete_ticket(payment_hash: str) -> None: @@ -210,4 +200,14 @@ async def get_event_tickets(event_id: str) -> list[Ticket]: {"event": event_id}, ) - return [Ticket(**_parse_ticket_row(row)) for row in rows] + tickets = [] + for row in rows: + # Convert empty strings back to None for the model + ticket_data = dict(row) + if ticket_data.get("name") == "": + ticket_data["name"] = None + if ticket_data.get("email") == "": + ticket_data["email"] = None + tickets.append(Ticket(**ticket_data)) + + return tickets