"""Blog app fragment endpoints. Exposes sexp fragments at ``/internal/fragments/`` for consumption by other coop apps via the fragment client. """ from __future__ import annotations from quart import Blueprint, Response, g, render_template, request from shared.infrastructure.fragments import FRAGMENT_HEADER from shared.services.navigation import get_navigation_tree 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/sexp") result = await handler() # nav-tree still returns HTML (Jinja template) for now ct = "text/html" if fragment_type == "nav-tree" else "text/sexp" return Response(result, status=200, content_type=ct) # --- nav-tree fragment (still Jinja for now — complex template) --- async def _nav_tree_handler(): app_name = request.args.get("app_name", "") path = request.args.get("path", "/") first_seg = path.strip("/").split("/")[0] menu_items = list(await get_navigation_tree(g.s)) class _NavItem: __slots__ = ("slug", "label", "feature_image") def __init__(self, slug, label, feature_image=None): self.slug = slug self.label = label self.feature_image = feature_image menu_items.append(_NavItem("artdag", "art-dag")) return await render_template( "fragments/nav_tree.html", menu_items=menu_items, frag_app_name=app_name, frag_first_seg=first_seg, ) _handlers["nav-tree"] = _nav_tree_handler # --- link-card fragment — returns sexp source --- def _blog_link_card_sexp(post, link: str) -> str: from shared.sexp.helpers import sexp_call published = post.published_at.strftime("%d %b %Y") if post.published_at else None return sexp_call("link-card", link=link, title=post.title, image=post.feature_image, icon="fas fa-file-alt", subtitle=post.custom_excerpt or post.excerpt, detail=published, data_app="blog") async def _link_card_handler(): from shared.services.registry import services from shared.infrastructure.urls import blog_url slug = request.args.get("slug", "") keys_raw = request.args.get("keys", "") # Batch mode if keys_raw: slugs = [k.strip() for k in keys_raw.split(",") if k.strip()] parts = [] for s in slugs: parts.append(f"") post = await services.blog.get_post_by_slug(g.s, s) if post: parts.append(_blog_link_card_sexp(post, blog_url(f"/{post.slug}"))) return "\n".join(parts) # Single mode if not slug: return "" post = await services.blog.get_post_by_slug(g.s, slug) if not post: return "" return _blog_link_card_sexp(post, blog_url(f"/{post.slug}")) _handlers["link-card"] = _link_card_handler bp._fragment_handlers = _handlers return bp