"""Relations app fragment endpoints. Generic container-nav fragment that renders navigation items for all related entities, driven by the relation registry. """ from __future__ import annotations from quart import Blueprint, Response, g, request from shared.infrastructure.fragments import FRAGMENT_HEADER def register(): bp = Blueprint("fragments", __name__, url_prefix="/internal/fragments") _handlers: dict[str, object] = {} @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): handler = _handlers.get(fragment_type) if handler is None: return Response("", status=200, content_type="text/html") html = await handler() return Response(html, status=200, content_type="text/html") # --- generic container-nav fragment ---------------------------------------- async def _container_nav_handler(): """Render nav items for all visible relations of a container entity. Query params: container_type: entity type (e.g. "page") container_id: entity id post_slug: used for URL construction exclude: comma-separated relation types to skip """ from shared.sexp.jinja_bridge import sexp as render_sexp from shared.sexp.relations import relations_from from shared.services.relationships import get_children from shared.infrastructure.urls import events_url, market_url _SERVICE_URL = { "calendar": events_url, "market": market_url, } container_type = request.args.get("container_type", "page") container_id = int(request.args.get("container_id", 0)) post_slug = request.args.get("post_slug", "") nav_class = request.args.get("nav_class", "") exclude_raw = request.args.get("exclude", "") exclude = set(exclude_raw.split(",")) if exclude_raw else set() nav_defs = [ d for d in relations_from(container_type) if d.nav != "hidden" and d.name not in exclude ] if not nav_defs: return "" parts = [] for defn in nav_defs: children = await get_children( g.s, parent_type=container_type, parent_id=container_id, child_type=defn.to_type, relation_type=defn.name, ) for child in children: slug = (child.metadata_ or {}).get("slug", "") if not slug: continue nav_label = defn.nav_label or "" if post_slug and nav_label: path = f"/{post_slug}/{nav_label}/{slug}/" elif post_slug: path = f"/{post_slug}/{slug}/" else: path = f"/{slug}/" url_fn = _SERVICE_URL.get(defn.to_type) href = url_fn(path) if url_fn else path parts.append(render_sexp( '(~relation-nav :href href :name name :icon icon :nav-class nav-class :relation-type relation-type)', href=href, name=child.label or "", icon=defn.nav_icon or "", **{ "nav-class": nav_class, "relation-type": defn.name, }, )) return "\n".join(parts) _handlers["container-nav"] = _container_nav_handler return bp