""" Internal JSON API for the coop app. These endpoints are called by other apps (market, cart) over HTTP to fetch Ghost CMS content and menu items without importing blog services. """ from __future__ import annotations from quart import Blueprint, g, jsonify from sqlalchemy import select from sqlalchemy.orm import selectinload from models.menu_item import MenuItem from suma_browser.app.csrf import csrf_exempt def register() -> Blueprint: bp = Blueprint("coop_api", __name__, url_prefix="/internal") @bp.get("/menu-items") @csrf_exempt async def menu_items(): """ Return all active menu items as lightweight JSON. Called by market and cart apps to render the nav. """ result = await g.s.execute( select(MenuItem) .where(MenuItem.deleted_at.is_(None)) .options(selectinload(MenuItem.post)) .order_by(MenuItem.sort_order.asc(), MenuItem.id.asc()) ) items = result.scalars().all() return jsonify( [ { "id": mi.id, "post": { "title": mi.post.title if mi.post else None, "slug": mi.post.slug if mi.post else None, "feature_image": mi.post.feature_image if mi.post else None, }, } for mi in items ] ) @bp.get("/post/") @csrf_exempt async def post_by_slug(slug: str): """ Return a Ghost post's key fields by slug. Called by market app for the landing page. """ from suma_browser.app.bp.blog.ghost_db import DBClient client = DBClient(g.s) posts = await client.posts_by_slug(slug, include_drafts=False) if not posts: return jsonify(None), 404 post, original_post = posts[0] return jsonify( { "post": { "id": post.get("id"), "title": post.get("title"), "html": post.get("html"), "custom_excerpt": post.get("custom_excerpt"), "feature_image": post.get("feature_image"), "slug": post.get("slug"), }, "original_post": { "id": getattr(original_post, "id", None), "title": getattr(original_post, "title", None), }, } ) return bp