from fastapi import APIRouter, Header, HTTPException, Query, Request, status from app.core.deps import DbSession, EditorAdmin, SettingsDep from app.schemas.admin import ( AdminLoginRequest, AdminToken, AdminUserPublic, DashboardMetrics, EventAdmin, EventCreate, EventStatsRow, EventUpdate, OrderAdmin, PaymentAdmin, TicketTypeAdmin, TicketTypeCreate, TicketTypeUpdate, VenueAdmin, VenueCreate, VenueUpdate, ) from app.schemas.tickets import TicketVerifyRequest, TicketVerifyResponse from app.core.config import get_settings from app.core.rate_limit import enforce_rate_limit from app.core.security import create_admin_access_token, verify_password from app.models.entities import AdminUser from app.models.enums import OrderStatus from app.services.admin.catalog import ( create_event, create_ticket_type, create_venue, list_events_admin, list_ticket_types, list_venues_admin, ticket_type_to_admin, update_event, update_ticket_type, update_venue, ) from app.services.admin.dashboard import get_dashboard_metrics, get_event_statistics from app.services.admin.orders import get_order_admin, list_orders_admin, list_payments_admin from app.services.tickets import assert_checkin_authorized, verify_ticket_at_gate router = APIRouter(prefix="/admin", tags=["admin"]) @router.post("/auth/login", response_model=AdminToken) def admin_login(body: AdminLoginRequest, request: Request, db: DbSession) -> AdminToken: enforce_rate_limit(request, scope="admin_login", limit=20, window_seconds=60) admin = db.query(AdminUser).filter(AdminUser.email == body.email.strip().lower()).first() if not admin or not verify_password(body.password, admin.hashed_password): raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials") token = create_admin_access_token(admin.id, get_settings()) return AdminToken(access_token=token) @router.get("/auth/me", response_model=AdminUserPublic) def admin_me(admin: EditorAdmin) -> AdminUser: return admin @router.get("/dashboard/metrics", response_model=DashboardMetrics) def dashboard_metrics(db: DbSession, _: EditorAdmin) -> DashboardMetrics: return get_dashboard_metrics(db) @router.get("/stats/events", response_model=list[EventStatsRow]) def event_statistics(db: DbSession, _: EditorAdmin) -> list[EventStatsRow]: return get_event_statistics(db) @router.get("/venues", response_model=list[VenueAdmin]) def venues_list(db: DbSession, _: EditorAdmin) -> list[VenueAdmin]: return [VenueAdmin.model_validate(v) for v in list_venues_admin(db)] @router.post("/venues", response_model=VenueAdmin, status_code=201) def venues_create(body: VenueCreate, db: DbSession, _: EditorAdmin) -> VenueAdmin: return VenueAdmin.model_validate(create_venue(db, body)) @router.patch("/venues/{venue_id}", response_model=VenueAdmin) def venues_update(venue_id: int, body: VenueUpdate, db: DbSession, _: EditorAdmin) -> VenueAdmin: return VenueAdmin.model_validate(update_venue(db, venue_id, body)) @router.get("/events", response_model=list[EventAdmin]) def events_list(db: DbSession, _: EditorAdmin) -> list[EventAdmin]: return [EventAdmin.model_validate(e) for e in list_events_admin(db)] @router.post("/events", response_model=EventAdmin, status_code=201) def events_create(body: EventCreate, db: DbSession, _: EditorAdmin) -> EventAdmin: return EventAdmin.model_validate(create_event(db, body)) @router.patch("/events/{event_id}", response_model=EventAdmin) def events_update(event_id: int, body: EventUpdate, db: DbSession, _: EditorAdmin) -> EventAdmin: return EventAdmin.model_validate(update_event(db, event_id, body)) @router.get("/ticket-types", response_model=list[TicketTypeAdmin]) def ticket_types_list( db: DbSession, _: EditorAdmin, event_id: int | None = Query(default=None), ) -> list[TicketTypeAdmin]: return [TicketTypeAdmin.model_validate(row) for row in list_ticket_types(db, event_id)] @router.post("/ticket-types", response_model=TicketTypeAdmin, status_code=201) def ticket_types_create(body: TicketTypeCreate, db: DbSession, _: EditorAdmin) -> TicketTypeAdmin: tier = create_ticket_type(db, body) return TicketTypeAdmin.model_validate(ticket_type_to_admin(tier)) @router.patch("/ticket-types/{tier_id}", response_model=TicketTypeAdmin) def ticket_types_update( tier_id: int, body: TicketTypeUpdate, db: DbSession, _: EditorAdmin, ) -> TicketTypeAdmin: tier = update_ticket_type(db, tier_id, body) return TicketTypeAdmin.model_validate(ticket_type_to_admin(tier)) @router.get("/orders", response_model=list[OrderAdmin]) def orders_list( db: DbSession, _: EditorAdmin, status: OrderStatus | None = Query(default=None), ) -> list[OrderAdmin]: return [OrderAdmin.model_validate(o) for o in list_orders_admin(db, status)] @router.get("/orders/{order_id}", response_model=OrderAdmin) def orders_detail(order_id: int, db: DbSession, _: EditorAdmin) -> OrderAdmin: return OrderAdmin.model_validate(get_order_admin(db, order_id)) @router.get("/payments", response_model=list[PaymentAdmin]) def payments_list(db: DbSession, _: EditorAdmin) -> list[PaymentAdmin]: return [PaymentAdmin.model_validate(row) for row in list_payments_admin(db)] @router.post("/tickets/verify", response_model=TicketVerifyResponse) def admin_verify_ticket( body: TicketVerifyRequest, db: DbSession, _: EditorAdmin, settings: SettingsDep, x_check_in_key: str | None = Header(default=None, alias="X-Check-In-Key"), ) -> TicketVerifyResponse: assert_checkin_authorized(settings, x_check_in_key) result = verify_ticket_at_gate(db, body.qr_token.strip()) return TicketVerifyResponse.model_validate(result)