Move Ghost membership sync from blog to account service so blog no longer queries account tables (users, ghost_labels, etc.). Account runs membership sync at startup and exposes HTTP action/data endpoints for webhook-triggered syncs and user lookups. Key changes: - account/services/ghost_membership.py: all membership sync functions - account/bp/actions + data: ghost-sync-member, user-by-email, newsletters - blog ghost_sync.py: stripped to content-only (posts, authors, tags) - blog webhook member: delegates to account via call_action() - try_publish: opens federation session when DBs differ - oauth.py callback: uses get_account_session() for OAuthCode - page_configs moved from db_events to db_blog in split script Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
113 lines
3.1 KiB
Python
113 lines
3.1 KiB
Python
# suma_browser/webhooks.py
|
|
from __future__ import annotations
|
|
import os
|
|
from quart import Blueprint, request, abort, Response, g
|
|
|
|
from ..ghost.ghost_sync import (
|
|
sync_single_page,
|
|
sync_single_post,
|
|
sync_single_author,
|
|
sync_single_tag,
|
|
)
|
|
from shared.browser.app.redis_cacher import clear_cache
|
|
from shared.browser.app.csrf import csrf_exempt
|
|
|
|
ghost_webhooks = Blueprint("ghost_webhooks", __name__, url_prefix="/__ghost-webhook")
|
|
|
|
def _check_secret(req) -> None:
|
|
expected = os.getenv("GHOST_WEBHOOK_SECRET")
|
|
if not expected:
|
|
return
|
|
got = req.args.get("secret") or req.headers.get("X-Webhook-Secret")
|
|
if got != expected:
|
|
abort(401)
|
|
|
|
def _extract_id(data: dict, key: str) -> str | None:
|
|
block = data.get(key) or {}
|
|
cur = block.get("current") or {}
|
|
prev = block.get("previous") or {}
|
|
return cur.get("id") or prev.get("id")
|
|
|
|
|
|
@csrf_exempt
|
|
@ghost_webhooks.route("/member/", methods=["POST"])
|
|
async def webhook_member() -> Response:
|
|
_check_secret(request)
|
|
|
|
data = await request.get_json(force=True, silent=True) or {}
|
|
ghost_id = _extract_id(data, "member")
|
|
if not ghost_id:
|
|
abort(400, "no member id")
|
|
|
|
# Delegate to account service (membership data lives in db_account)
|
|
from shared.infrastructure.actions import call_action
|
|
try:
|
|
await call_action(
|
|
"account", "ghost-sync-member",
|
|
payload={"ghost_id": ghost_id},
|
|
timeout=30.0,
|
|
)
|
|
except Exception as e:
|
|
import logging
|
|
logging.getLogger(__name__).error("Member sync via account failed: %s", e)
|
|
return Response(status=204)
|
|
|
|
@csrf_exempt
|
|
@ghost_webhooks.post("/post/")
|
|
@clear_cache(tag='blog')
|
|
async def webhook_post() -> Response:
|
|
_check_secret(request)
|
|
|
|
data = await request.get_json(force=True, silent=True) or {}
|
|
ghost_id = _extract_id(data, "post")
|
|
if not ghost_id:
|
|
abort(400, "no post id")
|
|
|
|
await sync_single_post(g.s, ghost_id)
|
|
|
|
return Response(status=204)
|
|
|
|
@csrf_exempt
|
|
@ghost_webhooks.post("/page/")
|
|
@clear_cache(tag='blog')
|
|
async def webhook_page() -> Response:
|
|
_check_secret(request)
|
|
|
|
data = await request.get_json(force=True, silent=True) or {}
|
|
ghost_id = _extract_id(data, "page")
|
|
if not ghost_id:
|
|
abort(400, "no page id")
|
|
|
|
await sync_single_page(g.s, ghost_id)
|
|
|
|
return Response(status=204)
|
|
|
|
@csrf_exempt
|
|
@ghost_webhooks.post("/author/")
|
|
@clear_cache(tag='blog')
|
|
async def webhook_author() -> Response:
|
|
_check_secret(request)
|
|
|
|
data = await request.get_json(force=True, silent=True) or {}
|
|
ghost_id = _extract_id(data, "user") or _extract_id(data, "author")
|
|
if not ghost_id:
|
|
abort(400, "no author id")
|
|
|
|
await sync_single_author(g.s, ghost_id)
|
|
|
|
return Response(status=204)
|
|
|
|
@csrf_exempt
|
|
@ghost_webhooks.post("/tag/")
|
|
@clear_cache(tag='blog')
|
|
async def webhook_tag() -> Response:
|
|
_check_secret(request)
|
|
|
|
data = await request.get_json(force=True, silent=True) or {}
|
|
ghost_id = _extract_id(data, "tag")
|
|
if not ghost_id:
|
|
abort(400, "no tag id")
|
|
|
|
await sync_single_tag(g.s, ghost_id)
|
|
return Response(status=204)
|