Add glue layer: MenuNode replaces MenuItem, remove /internal/menu-items API
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m4s

- Context processor: get_navigation_tree() replaces get_all_menu_items()
- Menu admin service: MenuItem → MenuNode (container_type/container_id pattern)
- Remove /internal/menu-items endpoint (other apps query menu_nodes directly)
- Remove menu_items relationship from Post model
- Templates: item.post.X → item.X
- Add glue submodule

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
giles
2026-02-11 23:37:43 +00:00
parent da3481196b
commit 05d9e70e8a
9 changed files with 84 additions and 124 deletions

View File

@@ -2,50 +2,18 @@
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.
to fetch Ghost CMS content without importing blog services.
"""
from __future__ import annotations
from quart import Blueprint, g, jsonify
from sqlalchemy import select
from sqlalchemy.orm import selectinload
from shared.models.menu_item import MenuItem
from shared.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/<slug>")
@csrf_exempt
async def post_by_slug(slug: str):