Decouple all cross-app service calls to HTTP endpoints
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m0s

Replace every direct cross-app services.* call with HTTP-based
communication: call_action() for writes, fetch_data() for reads.
Each app now registers only its own domain service.

Infrastructure:
- shared/infrastructure/actions.py — POST client for /internal/actions/
- shared/infrastructure/data_client.py — GET client for /internal/data/
- shared/contracts/dtos.py — dto_to_dict/dto_from_dict serialization

Action endpoints (writes):
- events: 8 handlers (ticket adjust, claim/confirm, toggle, adopt)
- market: 2 handlers (create/soft-delete marketplace)
- cart: 1 handler (adopt cart for user)

Data endpoints (reads):
- blog: 4 (post-by-slug/id, posts-by-ids, search-posts)
- events: 10 (pending entries/tickets, entries/tickets for page/order,
  entry-ids, associated-entries, calendars, visible-entries-for-period)
- market: 1 (marketplaces-for-container)
- cart: 1 (cart-summary)

Service registration cleanup:
- blog→blog+federation, events→calendar+federation,
  market→market+federation, cart→cart only,
  federation→federation only, account→nothing
- Stubs reduced to minimal StubFederationService

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
giles
2026-02-25 03:01:38 +00:00
parent 5dafbdbda9
commit 3b707ec8a0
55 changed files with 1210 additions and 581 deletions

View File

@@ -10,7 +10,7 @@ from sqlalchemy import select
from shared.infrastructure.factory import create_base_app
from shared.config import config
from bp import register_market_bp, register_all_markets, register_page_markets, register_fragments
from bp import register_market_bp, register_all_markets, register_page_markets, register_fragments, register_actions, register_data
async def market_context() -> dict:
@@ -23,9 +23,10 @@ async def market_context() -> dict:
"""
from shared.infrastructure.context import base_context
from shared.services.navigation import get_navigation_tree
from shared.services.registry import services
from shared.infrastructure.cart_identity import current_cart_identity
from shared.infrastructure.fragments import fetch_fragments
from shared.infrastructure.data_client import fetch_data
from shared.contracts.dtos import CartSummaryDTO, dto_from_dict
from shared.models.market import CartItem
from sqlalchemy.orm import selectinload
@@ -36,10 +37,14 @@ async def market_context() -> dict:
ident = current_cart_identity()
# cart_count/cart_total via service (consistent with blog/events apps)
summary = await services.cart.cart_summary(
g.s, user_id=ident["user_id"], session_id=ident["session_id"],
)
# cart_count/cart_total via internal data endpoint
summary_params = {}
if ident["user_id"] is not None:
summary_params["user_id"] = ident["user_id"]
if ident["session_id"] is not None:
summary_params["session_id"] = ident["session_id"]
raw = await fetch_data("cart", "cart-summary", params=summary_params, required=False)
summary = dto_from_dict(CartSummaryDTO, raw) if raw else CartSummaryDTO()
ctx["cart_count"] = summary.count + summary.calendar_count
ctx["cart_total"] = float(summary.total + summary.calendar_total)
@@ -80,7 +85,6 @@ async def market_context() -> dict:
def create_app() -> "Quart":
from models.market_place import MarketPlace
from shared.services.registry import services
from services import register_domain_services
app = create_base_app(
@@ -118,6 +122,8 @@ def create_app() -> "Quart":
)
app.register_blueprint(register_fragments())
app.register_blueprint(register_actions())
app.register_blueprint(register_data())
# --- Auto-inject slugs into url_for() calls ---
@app.url_value_preprocessor
@@ -147,26 +153,27 @@ def create_app() -> "Quart":
# --- Load post and market data ---
@app.before_request
async def hydrate_market():
from shared.infrastructure.data_client import fetch_data
post_slug = getattr(g, "post_slug", None)
market_slug = getattr(g, "market_slug", None)
if not post_slug:
return
# Load post by slug via blog service
post = await services.blog.get_post_by_slug(g.s, post_slug)
# Load post by slug via blog data endpoint
post = await fetch_data("blog", "post-by-slug", params={"slug": post_slug})
if not post:
abort(404)
g.post_data = {
"post": {
"id": post.id,
"title": post.title,
"slug": post.slug,
"feature_image": post.feature_image,
"html": post.html,
"status": post.status,
"visibility": post.visibility,
"is_page": post.is_page,
"id": post["id"],
"title": post["title"],
"slug": post["slug"],
"feature_image": post.get("feature_image"),
"html": post.get("html"),
"status": post["status"],
"visibility": post["visibility"],
"is_page": post.get("is_page", False),
},
}