Compare commits

..

No commits in common. "eb474b139033a805de0f21fe5de5fbfd62290c19" and "a77145e08ee1534e2bda1d014ff580a71675aff1" have entirely different histories.

4 changed files with 47 additions and 137 deletions

90
crud.py
View file

@ -1,4 +1,3 @@
import json
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from typing import Optional from typing import Optional
@ -7,30 +6,6 @@ from lnbits.helpers import urlsafe_short_hash
from .models import CreateEvent, Event, Ticket, TicketExtra 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") db = Database("ext_events")
@ -119,7 +94,14 @@ async def get_ticket(payment_hash: str) -> Optional[Ticket]:
if not row: if not row:
return None 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]: async def get_tickets(wallet_ids: str | list[str]) -> list[Ticket]:
@ -128,7 +110,17 @@ async def get_tickets(wallet_ids: str | list[str]) -> list[Ticket]:
q = ",".join([f"'{wallet_id}'" for wallet_id in wallet_ids]) q = ",".join([f"'{wallet_id}'" for wallet_id in wallet_ids])
rows = await db.fetchall(f"SELECT * FROM events.ticket WHERE wallet IN ({q})") 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]: async def get_tickets_by_user_id(user_id: str) -> list[Ticket]:
@ -138,7 +130,17 @@ async def get_tickets_by_user_id(user_id: str) -> list[Ticket]:
{"user_id": user_id} {"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: async def delete_ticket(payment_hash: str) -> None:
@ -200,26 +202,6 @@ async def get_all_events() -> list[Event]:
) )
async def get_public_events() -> list[Event]:
"""Get approved, non-canceled events for public display."""
return await db.fetchall(
"""
SELECT * FROM events.events
WHERE status = 'approved' AND canceled = FALSE
ORDER BY event_start_date ASC
""",
model=Event,
)
async def get_pending_events() -> list[Event]:
"""Get proposed events awaiting admin approval."""
return await db.fetchall(
"SELECT * FROM events.events WHERE status = 'proposed' ORDER BY time DESC",
model=Event,
)
async def delete_event(event_id: str) -> None: async def delete_event(event_id: str) -> None:
await db.execute("DELETE FROM events.events WHERE id = :id", {"id": event_id}) await db.execute("DELETE FROM events.events WHERE id = :id", {"id": event_id})
@ -230,4 +212,14 @@ async def get_event_tickets(event_id: str) -> list[Ticket]:
{"event": event_id}, {"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

View file

@ -191,14 +191,3 @@ async def m007_add_extra_fields(db):
# Add 'extra' column to ticket table # Add 'extra' column to ticket table
await db.execute("ALTER TABLE events.ticket ADD COLUMN extra TEXT;") await db.execute("ALTER TABLE events.ticket ADD COLUMN extra TEXT;")
async def m008_add_event_status(db):
"""
Add status column to events table for proposal/approval workflow.
Values: 'proposed', 'approved', 'rejected'.
Default 'approved' for backward compatibility with existing events.
"""
await db.execute(
"ALTER TABLE events.events ADD COLUMN status TEXT NOT NULL DEFAULT 'approved';"
)

View file

@ -39,7 +39,6 @@ class CreateEvent(BaseModel):
price_per_ticket: float = Query(..., ge=0) price_per_ticket: float = Query(..., ge=0)
banner: Optional[str] = None banner: Optional[str] = None
extra: EventExtra = Field(default_factory=EventExtra) extra: EventExtra = Field(default_factory=EventExtra)
status: str = "approved" # proposed, approved, rejected
class CreateTicket(BaseModel): class CreateTicket(BaseModel):
@ -79,7 +78,6 @@ class Event(BaseModel):
sold: int = 0 sold: int = 0
banner: str | None = None banner: str | None = None
extra: EventExtra = Field(default_factory=EventExtra) extra: EventExtra = Field(default_factory=EventExtra)
status: str = "approved" # proposed, approved, rejected
class TicketExtra(BaseModel): class TicketExtra(BaseModel):

View file

@ -24,8 +24,6 @@ from .crud import (
get_event, get_event,
get_event_tickets, get_event_tickets,
get_events, get_events,
get_pending_events,
get_public_events,
get_ticket, get_ticket,
get_tickets, get_tickets,
get_tickets_by_user_id, get_tickets_by_user_id,
@ -56,10 +54,12 @@ async def api_events(
@events_api_router.get("/api/v1/events/public") @events_api_router.get("/api/v1/events/public")
async def api_events_public(): async def api_events_public():
""" """
Retrieve approved, non-canceled events for public display. Retrieve all events in the database with read-only access.
No authentication required. This endpoint allows access to all events using any valid API key (read access).
""" """
events = await get_public_events() # Get all events from the database without wallet filtering
from .crud import get_all_events
events = await get_all_events()
return [event.dict() for event in events] return [event.dict() for event in events]
@ -128,75 +128,6 @@ async def api_form_delete(
return "", HTTPStatus.NO_CONTENT return "", HTTPStatus.NO_CONTENT
#########Event Approval##########
@events_api_router.post("/api/v1/events/propose")
async def api_event_propose(
data: CreateEvent,
wallet: WalletTypeInfo = Depends(require_invoice_key),
):
"""
Propose a new event for admin approval.
Requires invoice key (any authenticated user, not admin-only).
"""
data.status = "proposed"
data.wallet = wallet.wallet.id
event = await create_event(data)
return event.dict()
@events_api_router.get("/api/v1/events/pending")
async def api_events_pending(
wallet: WalletTypeInfo = Depends(require_admin_key),
):
"""Get all proposed events awaiting approval. Admin only."""
events = await get_pending_events()
return [event.dict() for event in events]
@events_api_router.put("/api/v1/events/{event_id}/approve")
async def api_event_approve(
event_id: str,
wallet: WalletTypeInfo = Depends(require_admin_key),
):
"""Approve a proposed event. Admin only."""
event = await get_event(event_id)
if not event:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Event does not exist."
)
if event.status != "proposed":
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=f"Event is already {event.status}.",
)
event.status = "approved"
event = await update_event(event)
return event.dict()
@events_api_router.put("/api/v1/events/{event_id}/reject")
async def api_event_reject(
event_id: str,
wallet: WalletTypeInfo = Depends(require_admin_key),
):
"""Reject a proposed event. Admin only."""
event = await get_event(event_id)
if not event:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Event does not exist."
)
if event.status != "proposed":
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=f"Event is already {event.status}.",
)
event.status = "rejected"
event = await update_event(event)
return event.dict()
#########Tickets########## #########Tickets##########
@ -276,7 +207,7 @@ async def api_ticket_make_ticket_user_id(event_id: str, user_id: str):
@events_api_router.get("/api/v1/tickets/{event_id}/{name}/{email}") @events_api_router.get("/api/v1/tickets/{event_id}/{name}/{email}")
async def api_ticket_make_ticket(event_id, name, email, promo_code=None, refund_address=None): async def api_ticket_make_ticket(event_id, name, email, promo_code, refund_address):
event = await get_event(event_id) event = await get_event(event_id)
if not event: if not event:
raise HTTPException( raise HTTPException(