Fix cross-DB queries: move page_configs to cart, fix OAuth code_hash lookup
page_configs table lives in db_cart but blog was querying it directly, causing UndefinedTableError. Move all PageConfig read/write endpoints to cart service and have blog proxy via fetch_data/call_action. Also fix OAuth callback to use code_hash lookup (codes are now stored hashed) and pass grant_token in redirect URL to prevent auth loops. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -22,24 +22,23 @@ def register():
|
||||
@require_admin
|
||||
async def admin(slug: str):
|
||||
from shared.browser.app.utils.htmx import is_htmx_request
|
||||
from shared.models.page_config import PageConfig
|
||||
from sqlalchemy import select as sa_select
|
||||
from shared.infrastructure.data_client import fetch_data
|
||||
|
||||
# Load features for page admin
|
||||
# Load features for page admin (page_configs lives in db_cart)
|
||||
post = (g.post_data or {}).get("post", {})
|
||||
features = {}
|
||||
sumup_configured = False
|
||||
sumup_merchant_code = ""
|
||||
sumup_checkout_prefix = ""
|
||||
if post.get("is_page"):
|
||||
pc = (await g.s.execute(
|
||||
sa_select(PageConfig).where(PageConfig.container_type == "page", PageConfig.container_id == post["id"])
|
||||
)).scalar_one_or_none()
|
||||
if pc:
|
||||
features = pc.features or {}
|
||||
sumup_configured = bool(pc.sumup_api_key)
|
||||
sumup_merchant_code = pc.sumup_merchant_code or ""
|
||||
sumup_checkout_prefix = pc.sumup_checkout_prefix or ""
|
||||
raw_pc = await fetch_data("cart", "page-config",
|
||||
params={"container_type": "page", "container_id": post["id"]},
|
||||
required=False)
|
||||
if raw_pc:
|
||||
features = raw_pc.get("features") or {}
|
||||
sumup_configured = bool(raw_pc.get("sumup_api_key"))
|
||||
sumup_merchant_code = raw_pc.get("sumup_merchant_code") or ""
|
||||
sumup_checkout_prefix = raw_pc.get("sumup_checkout_prefix") or ""
|
||||
|
||||
ctx = {
|
||||
"features": features,
|
||||
@@ -61,12 +60,9 @@ def register():
|
||||
@bp.put("/features/")
|
||||
@require_admin
|
||||
async def update_features(slug: str):
|
||||
"""Update PageConfig.features for a page."""
|
||||
from shared.models.page_config import PageConfig
|
||||
from models.ghost_content import Post
|
||||
from sqlalchemy import select as sa_select
|
||||
"""Update PageConfig.features for a page (page_configs lives in db_cart)."""
|
||||
from shared.infrastructure.actions import call_action
|
||||
from quart import jsonify
|
||||
import json
|
||||
|
||||
post = g.post_data.get("post")
|
||||
if not post or not post.get("is_page"):
|
||||
@@ -74,21 +70,9 @@ def register():
|
||||
|
||||
post_id = post["id"]
|
||||
|
||||
# Load or create PageConfig
|
||||
pc = (await g.s.execute(
|
||||
sa_select(PageConfig).where(PageConfig.container_type == "page", PageConfig.container_id == post_id)
|
||||
)).scalar_one_or_none()
|
||||
if pc is None:
|
||||
pc = PageConfig(container_type="page", container_id=post_id, features={})
|
||||
g.s.add(pc)
|
||||
await g.s.flush()
|
||||
from shared.services.relationships import attach_child
|
||||
await attach_child(g.s, "page", post_id, "page_config", pc.id)
|
||||
|
||||
# Parse request body
|
||||
body = await request.get_json()
|
||||
if body is None:
|
||||
# Fall back to form data
|
||||
form = await request.form
|
||||
body = {}
|
||||
for key in ("calendar", "market"):
|
||||
@@ -99,38 +83,30 @@ def register():
|
||||
if not isinstance(body, dict):
|
||||
return jsonify({"error": "Expected JSON object with feature flags."}), 400
|
||||
|
||||
# Merge features
|
||||
features = dict(pc.features or {})
|
||||
for key, val in body.items():
|
||||
if isinstance(val, bool):
|
||||
features[key] = val
|
||||
elif val in ("true", "1", "on"):
|
||||
features[key] = True
|
||||
elif val in ("false", "0", "off", None):
|
||||
features[key] = False
|
||||
# Update via cart action (page_configs lives in db_cart)
|
||||
result = await call_action("cart", "update-page-config", payload={
|
||||
"container_type": "page",
|
||||
"container_id": post_id,
|
||||
"features": body,
|
||||
})
|
||||
|
||||
pc.features = features
|
||||
from sqlalchemy.orm.attributes import flag_modified
|
||||
flag_modified(pc, "features")
|
||||
await g.s.flush()
|
||||
features = result.get("features", {})
|
||||
|
||||
# Return updated features panel
|
||||
html = await render_template(
|
||||
"_types/post/admin/_features_panel.html",
|
||||
features=features,
|
||||
post=post,
|
||||
sumup_configured=bool(pc.sumup_api_key),
|
||||
sumup_merchant_code=pc.sumup_merchant_code or "",
|
||||
sumup_checkout_prefix=pc.sumup_checkout_prefix or "",
|
||||
sumup_configured=result.get("sumup_configured", False),
|
||||
sumup_merchant_code=result.get("sumup_merchant_code") or "",
|
||||
sumup_checkout_prefix=result.get("sumup_checkout_prefix") or "",
|
||||
)
|
||||
return await make_response(html)
|
||||
|
||||
@bp.put("/admin/sumup/")
|
||||
@require_admin
|
||||
async def update_sumup(slug: str):
|
||||
"""Update PageConfig SumUp credentials for a page."""
|
||||
from shared.models.page_config import PageConfig
|
||||
from sqlalchemy import select as sa_select
|
||||
"""Update PageConfig SumUp credentials for a page (page_configs lives in db_cart)."""
|
||||
from shared.infrastructure.actions import call_action
|
||||
from quart import jsonify
|
||||
|
||||
post = g.post_data.get("post")
|
||||
@@ -139,37 +115,30 @@ def register():
|
||||
|
||||
post_id = post["id"]
|
||||
|
||||
pc = (await g.s.execute(
|
||||
sa_select(PageConfig).where(PageConfig.container_type == "page", PageConfig.container_id == post_id)
|
||||
)).scalar_one_or_none()
|
||||
if pc is None:
|
||||
pc = PageConfig(container_type="page", container_id=post_id, features={})
|
||||
g.s.add(pc)
|
||||
await g.s.flush()
|
||||
from shared.services.relationships import attach_child
|
||||
await attach_child(g.s, "page", post_id, "page_config", pc.id)
|
||||
|
||||
form = await request.form
|
||||
merchant_code = (form.get("merchant_code") or "").strip()
|
||||
api_key = (form.get("api_key") or "").strip()
|
||||
checkout_prefix = (form.get("checkout_prefix") or "").strip()
|
||||
|
||||
pc.sumup_merchant_code = merchant_code or None
|
||||
pc.sumup_checkout_prefix = checkout_prefix or None
|
||||
# Only update API key if non-empty (allows updating other fields without re-entering key)
|
||||
payload: dict = {
|
||||
"container_type": "page",
|
||||
"container_id": post_id,
|
||||
"sumup_merchant_code": merchant_code or None,
|
||||
"sumup_checkout_prefix": checkout_prefix or None,
|
||||
}
|
||||
if api_key:
|
||||
pc.sumup_api_key = api_key
|
||||
payload["sumup_api_key"] = api_key
|
||||
|
||||
await g.s.flush()
|
||||
result = await call_action("cart", "update-page-config", payload=payload)
|
||||
|
||||
features = pc.features or {}
|
||||
features = result.get("features", {})
|
||||
html = await render_template(
|
||||
"_types/post/admin/_features_panel.html",
|
||||
features=features,
|
||||
post=post,
|
||||
sumup_configured=bool(pc.sumup_api_key),
|
||||
sumup_merchant_code=pc.sumup_merchant_code or "",
|
||||
sumup_checkout_prefix=pc.sumup_checkout_prefix or "",
|
||||
sumup_configured=result.get("sumup_configured", False),
|
||||
sumup_merchant_code=result.get("sumup_merchant_code") or "",
|
||||
sumup_checkout_prefix=result.get("sumup_checkout_prefix") or "",
|
||||
)
|
||||
return await make_response(html)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user