"""Blog app data endpoints. Exposes read-only JSON queries at ``/internal/data/`` for cross-app callers via the internal data client. """ from __future__ import annotations from quart import Blueprint, g, jsonify, request from shared.infrastructure.data_client import DATA_HEADER from shared.contracts.dtos import dto_to_dict from services import blog_service def register() -> Blueprint: bp = Blueprint("data", __name__, url_prefix="/internal/data") @bp.before_request async def _require_data_header(): if not request.headers.get(DATA_HEADER): return jsonify({"error": "forbidden"}), 403 from shared.infrastructure.internal_auth import validate_internal_request if not validate_internal_request(): return jsonify({"error": "forbidden"}), 403 _handlers: dict[str, object] = {} @bp.get("/") async def handle_query(query_name: str): handler = _handlers.get(query_name) if handler is None: return jsonify({"error": "unknown query"}), 404 result = await handler() return jsonify(result) # --- post-by-slug --- async def _post_by_slug(): slug = request.args.get("slug", "") post = await blog_service.get_post_by_slug(g.s, slug) if not post: return None return dto_to_dict(post) _handlers["post-by-slug"] = _post_by_slug # --- post-by-id --- async def _post_by_id(): post_id = int(request.args.get("id", 0)) post = await blog_service.get_post_by_id(g.s, post_id) if not post: return None return dto_to_dict(post) _handlers["post-by-id"] = _post_by_id # --- posts-by-ids --- async def _posts_by_ids(): ids_raw = request.args.get("ids", "") if not ids_raw: return [] ids = [int(x.strip()) for x in ids_raw.split(",") if x.strip()] posts = await blog_service.get_posts_by_ids(g.s, ids) return [dto_to_dict(p) for p in posts] _handlers["posts-by-ids"] = _posts_by_ids # --- search-posts --- async def _search_posts(): query = request.args.get("query", "") page = int(request.args.get("page", 1)) per_page = int(request.args.get("per_page", 10)) posts, total = await blog_service.search_posts(g.s, query, page, per_page) return {"posts": [dto_to_dict(p) for p in posts], "total": total} _handlers["search-posts"] = _search_posts # --- page-config-ensure --- async def _page_config_ensure(): """Get or create a PageConfig for a container_type + container_id.""" from sqlalchemy import select from shared.models.page_config import PageConfig container_type = request.args.get("container_type", "page") container_id = request.args.get("container_id", type=int) if container_id is None: return {"error": "container_id required"}, 400 row = (await g.s.execute( select(PageConfig).where( PageConfig.container_type == container_type, PageConfig.container_id == container_id, ) )).scalar_one_or_none() if row is None: row = PageConfig( container_type=container_type, container_id=container_id, features={}, ) g.s.add(row) await g.s.flush() return { "id": row.id, "container_type": row.container_type, "container_id": row.container_id, } _handlers["page-config-ensure"] = _page_config_ensure # --- page-config --- async def _page_config(): """Return a single PageConfig by container_type + container_id.""" from sqlalchemy import select from shared.models.page_config import PageConfig ct = request.args.get("container_type", "page") cid = request.args.get("container_id", type=int) if cid is None: return None pc = (await g.s.execute( select(PageConfig).where( PageConfig.container_type == ct, PageConfig.container_id == cid, ) )).scalar_one_or_none() if not pc: return None return _page_config_dict(pc) _handlers["page-config"] = _page_config # --- page-config-by-id --- async def _page_config_by_id(): """Return a single PageConfig by its primary key.""" from shared.models.page_config import PageConfig pc_id = request.args.get("id", type=int) if pc_id is None: return None pc = await g.s.get(PageConfig, pc_id) if not pc: return None return _page_config_dict(pc) _handlers["page-config-by-id"] = _page_config_by_id # --- page-configs-batch --- async def _page_configs_batch(): """Return PageConfigs for multiple container_ids (comma-separated).""" from sqlalchemy import select from shared.models.page_config import PageConfig ct = request.args.get("container_type", "page") ids_raw = request.args.get("ids", "") if not ids_raw: return [] ids = [int(x.strip()) for x in ids_raw.split(",") if x.strip()] if not ids: return [] result = await g.s.execute( select(PageConfig).where( PageConfig.container_type == ct, PageConfig.container_id.in_(ids), ) ) return [_page_config_dict(pc) for pc in result.scalars().all()] _handlers["page-configs-batch"] = _page_configs_batch return bp def _page_config_dict(pc) -> dict: """Serialize PageConfig to a JSON-safe dict.""" return { "id": pc.id, "container_type": pc.container_type, "container_id": pc.container_id, "features": pc.features or {}, "sumup_merchant_code": pc.sumup_merchant_code, "sumup_api_key": pc.sumup_api_key, "sumup_checkout_prefix": pc.sumup_checkout_prefix, }