Compare commits
No commits in common. "eb474b139033a805de0f21fe5de5fbfd62290c19" and "a77145e08ee1534e2bda1d014ff580a71675aff1" have entirely different histories.
eb474b1390
...
a77145e08e
4 changed files with 47 additions and 137 deletions
90
crud.py
90
crud.py
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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';"
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
81
views_api.py
81
views_api.py
|
|
@ -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(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue