Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
Extract blog-specific code from the coop monolith into a standalone repository. Includes auth, blog, post, admin, menu_items, snippets blueprints, associated templates, Dockerfile (APP_MODULE=app:app), entrypoint, and Gitea CI workflow. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
84 lines
2.5 KiB
Python
84 lines
2.5 KiB
Python
"""
|
|
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/<slug>")
|
|
@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
|