"""Events app fragment endpoints. Exposes sx fragments at ``/internal/fragments/`` for consumption by other coop apps via the fragment client. All handlers are defined declaratively in .sx files under ``events/sx/handlers/`` and dispatched via the sx handler registry. container-cards and account-page remain as Python handlers because they call domain service methods and return batched/conditional content, but they use sx_call() for rendering (no Jinja templates). """ from __future__ import annotations from quart import Blueprint, Response, g, request from shared.infrastructure.fragments import FRAGMENT_HEADER from shared.services.registry import services from shared.sx.handlers import get_handler, execute_handler def register(): bp = Blueprint("fragments", __name__, url_prefix="/internal/fragments") _handlers: dict[str, object] = {} # Fragment types that return HTML (comment-delimited batch) _html_types = {"container-cards"} @bp.before_request async def _require_fragment_header(): if not request.headers.get(FRAGMENT_HEADER): return Response("", status=403) @bp.get("/") async def get_fragment(fragment_type: str): # 1. Check Python handlers first handler = _handlers.get(fragment_type) if handler is not None: result = await handler() ct = "text/html" if fragment_type in _html_types else "text/sx" return Response(result, status=200, content_type=ct) # 2. Check sx handler registry handler_def = get_handler("events", fragment_type) if handler_def is not None: result = await execute_handler( handler_def, "events", args=dict(request.args), ) return Response(result, status=200, content_type="text/sx") return Response("", status=200, content_type="text/sx") # --- container-cards fragment: entries for blog listing cards ----------- # Returns text/html with comment markers # so the blog consumer can split per-post fragments. async def _container_cards_handler(): from sx.sx_components import render_fragment_container_cards post_ids_raw = request.args.get("post_ids", "") post_slugs_raw = request.args.get("post_slugs", "") post_ids = [int(x) for x in post_ids_raw.split(",") if x.strip()] post_slugs = [x.strip() for x in post_slugs_raw.split(",") if x.strip()] if not post_ids: return "" slug_map = {} for i, pid in enumerate(post_ids): slug_map[pid] = post_slugs[i] if i < len(post_slugs) else "" batch = await services.calendar.confirmed_entries_for_posts(g.s, post_ids) return render_fragment_container_cards(batch, post_ids, slug_map) _handlers["container-cards"] = _container_cards_handler # --- account-page fragment: tickets or bookings panel ------------------ # Returns text/sx — the account app embeds this as sx source. async def _account_page_handler(): from sx.sx_components import ( render_fragment_account_tickets, render_fragment_account_bookings, ) slug = request.args.get("slug", "") user_id = request.args.get("user_id", type=int) if not user_id: return "" if slug == "tickets": tickets = await services.calendar.user_tickets(g.s, user_id=user_id) return render_fragment_account_tickets(tickets) elif slug == "bookings": bookings = await services.calendar.user_bookings(g.s, user_id=user_id) return render_fragment_account_bookings(bookings) return "" _handlers["account-page"] = _account_page_handler bp._fragment_handlers = _handlers return bp