This repository has been archived on 2026-02-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blog/bp/coop_api.py
giles 8f7a15186c
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
feat: initialize blog app with blueprints and templates
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>
2026-02-09 23:15:56 +00:00

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