Add tickets & bookings to account page

Add TicketDTO, user_tickets/user_bookings to CalendarService protocol
and SqlCalendarService implementation, plus nav links and panel
templates for the auth account sub-pages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
giles
2026-02-19 16:06:21 +00:00
parent 98c3df860b
commit dfc324b1be
7 changed files with 180 additions and 2 deletions

View File

@@ -9,8 +9,8 @@ from sqlalchemy import select, update, func
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from shared.models.calendars import Calendar, CalendarEntry, CalendarEntryPost
from shared.contracts.dtos import CalendarDTO, CalendarEntryDTO
from shared.models.calendars import Calendar, CalendarEntry, CalendarEntryPost, Ticket
from shared.contracts.dtos import CalendarDTO, CalendarEntryDTO, TicketDTO
def _cal_to_dto(cal: Calendar) -> CalendarDTO:
@@ -47,6 +47,24 @@ def _entry_to_dto(entry: CalendarEntry) -> CalendarEntryDTO:
)
def _ticket_to_dto(ticket: Ticket) -> TicketDTO:
entry = getattr(ticket, "entry", None)
tt = getattr(ticket, "ticket_type", None)
cal = getattr(entry, "calendar", None) if entry else None
return TicketDTO(
id=ticket.id,
code=ticket.code,
state=ticket.state,
entry_name=entry.name if entry else "",
entry_start_at=entry.start_at if entry else ticket.created_at,
entry_end_at=entry.end_at if entry else None,
ticket_type_name=tt.name if tt else None,
calendar_name=cal.name if cal else None,
created_at=ticket.created_at,
checked_in_at=ticket.checked_in_at,
)
class SqlCalendarService:
# -- reads ----------------------------------------------------------------
@@ -210,6 +228,38 @@ class SqlCalendarService:
)
return [_entry_to_dto(e) for e in result.scalars().all()]
async def user_tickets(
self, session: AsyncSession, *, user_id: int,
) -> list[TicketDTO]:
result = await session.execute(
select(Ticket)
.where(
Ticket.user_id == user_id,
Ticket.state != "cancelled",
)
.order_by(Ticket.created_at.desc())
.options(
selectinload(Ticket.entry).selectinload(CalendarEntry.calendar),
selectinload(Ticket.ticket_type),
)
)
return [_ticket_to_dto(t) for t in result.scalars().all()]
async def user_bookings(
self, session: AsyncSession, *, user_id: int,
) -> list[CalendarEntryDTO]:
result = await session.execute(
select(CalendarEntry)
.where(
CalendarEntry.user_id == user_id,
CalendarEntry.deleted_at.is_(None),
CalendarEntry.state.in_(("ordered", "provisional", "confirmed")),
)
.order_by(CalendarEntry.start_at.desc())
.options(selectinload(CalendarEntry.calendar))
)
return [_entry_to_dto(e) for e in result.scalars().all()]
# -- batch reads (not in protocol — convenience for blog service) ---------
async def confirmed_entries_for_posts(