"""Cart app action endpoints. Exposes write operations at ``/internal/actions/`` for cross-app callers (login handler) via the internal action client. """ from __future__ import annotations from quart import Blueprint, g, jsonify, request from shared.infrastructure.actions import ACTION_HEADER from shared.services.registry import services def register() -> Blueprint: bp = Blueprint("actions", __name__, url_prefix="/internal/actions") @bp.before_request async def _require_action_header(): if not request.headers.get(ACTION_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.post("/") async def handle_action(action_name: str): handler = _handlers.get(action_name) if handler is None: return jsonify({"error": "unknown action"}), 404 try: result = await handler() return jsonify(result) except Exception as exc: import logging logging.getLogger(__name__).exception("Action %s failed", action_name) return jsonify({"error": str(exc)}), 500 # --- adopt-cart-for-user --- async def _adopt_cart(): data = await request.get_json() await services.cart.adopt_cart_for_user( g.s, data["user_id"], data["session_id"], ) return {"ok": True} _handlers["adopt-cart-for-user"] = _adopt_cart # --- update-page-config --- async def _update_page_config(): """Create or update a PageConfig (page_configs lives in db_cart).""" from shared.models.page_config import PageConfig from sqlalchemy import select from sqlalchemy.orm.attributes import flag_modified data = await request.get_json(force=True) container_type = data.get("container_type", "page") container_id = data.get("container_id") if container_id is None: return {"error": "container_id required"}, 400 pc = (await g.s.execute( select(PageConfig).where( PageConfig.container_type == container_type, PageConfig.container_id == container_id, ) )).scalar_one_or_none() if pc is None: pc = PageConfig( container_type=container_type, container_id=container_id, features=data.get("features", {}), ) g.s.add(pc) await g.s.flush() if "features" in data: features = dict(pc.features or {}) for key, val in data["features"].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 pc.features = features flag_modified(pc, "features") if "sumup_merchant_code" in data: pc.sumup_merchant_code = data["sumup_merchant_code"] or None if "sumup_checkout_prefix" in data: pc.sumup_checkout_prefix = data["sumup_checkout_prefix"] or None if "sumup_api_key" in data: pc.sumup_api_key = data["sumup_api_key"] or None await g.s.flush() 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_checkout_prefix": pc.sumup_checkout_prefix, "sumup_configured": bool(pc.sumup_api_key), } _handlers["update-page-config"] = _update_page_config # --- attach-child --- async def _attach_child(): """Create or revive a ContainerRelation.""" from shared.services.relationships import attach_child data = await request.get_json(force=True) rel = await attach_child( g.s, parent_type=data["parent_type"], parent_id=data["parent_id"], child_type=data["child_type"], child_id=data["child_id"], label=data.get("label"), sort_order=data.get("sort_order"), ) return { "id": rel.id, "parent_type": rel.parent_type, "parent_id": rel.parent_id, "child_type": rel.child_type, "child_id": rel.child_id, "sort_order": rel.sort_order, } _handlers["attach-child"] = _attach_child # --- detach-child --- async def _detach_child(): """Soft-delete a ContainerRelation.""" from shared.services.relationships import detach_child data = await request.get_json(force=True) deleted = await detach_child( g.s, parent_type=data["parent_type"], parent_id=data["parent_id"], child_type=data["child_type"], child_id=data["child_id"], ) return {"deleted": deleted} _handlers["detach-child"] = _detach_child return bp