Domain isolation: replace cross-domain imports with service calls
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 44s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 44s
Replace direct Post, Calendar, CalendarEntry model queries and glue lifecycle imports with typed service calls. Cart registers all 4 services via domain_services_fn with has() guards. Key changes: - app.py: use domain_services_fn, Post query → services.blog - api.py: Calendar/CalendarEntry → services.calendar - checkout: glue order_lifecycle → services.calendar.claim/confirm - calendar_cart: CalendarEntry → services.calendar.pending_entries() - page_cart: Post/Calendar queries → services.blog/calendar - global_routes: glue imports → service calls Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
13
app.py
13
app.py
@@ -46,7 +46,7 @@ async def cart_context() -> dict:
|
||||
Global cart_count / cart_total stay global for cart-mini.
|
||||
"""
|
||||
from shared.infrastructure.context import base_context
|
||||
from glue.services.navigation import get_navigation_tree
|
||||
from shared.services.navigation import get_navigation_tree
|
||||
|
||||
ctx = await base_context()
|
||||
|
||||
@@ -81,13 +81,14 @@ async def cart_context() -> dict:
|
||||
|
||||
|
||||
def create_app() -> "Quart":
|
||||
from shared.models.ghost_content import Post
|
||||
from shared.models.page_config import PageConfig
|
||||
from services import register_domain_services
|
||||
|
||||
app = create_base_app(
|
||||
"cart",
|
||||
context_fn=cart_context,
|
||||
before_request_fns=[_load_cart],
|
||||
domain_services_fn=register_domain_services,
|
||||
)
|
||||
|
||||
# App-specific templates override shared templates
|
||||
@@ -116,12 +117,8 @@ def create_app() -> "Quart":
|
||||
slug = getattr(g, "page_slug", None)
|
||||
if not slug:
|
||||
return
|
||||
post = (
|
||||
await g.s.execute(
|
||||
select(Post).where(Post.slug == slug, Post.is_page == True) # noqa: E712
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
if not post:
|
||||
post = await services.blog.get_post_by_slug(g.s, slug)
|
||||
if not post or not post.is_page:
|
||||
abort(404)
|
||||
g.page_post = post
|
||||
g.page_config = (
|
||||
|
||||
@@ -12,10 +12,9 @@ from sqlalchemy.orm import selectinload
|
||||
|
||||
from shared.models.market import CartItem
|
||||
from shared.models.market_place import MarketPlace
|
||||
from shared.models.calendars import CalendarEntry, Calendar
|
||||
from shared.models.ghost_content import Post
|
||||
from shared.browser.app.csrf import csrf_exempt
|
||||
from shared.infrastructure.cart_identity import current_cart_identity
|
||||
from shared.services.registry import services
|
||||
|
||||
|
||||
def register() -> Blueprint:
|
||||
@@ -38,12 +37,8 @@ def register() -> Blueprint:
|
||||
page_slug = request.args.get("page_slug")
|
||||
page_post_id = None
|
||||
if page_slug:
|
||||
post = (
|
||||
await g.s.execute(
|
||||
select(Post).where(Post.slug == page_slug, Post.is_page == True) # noqa: E712
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
if post:
|
||||
post = await services.blog.get_post_by_slug(g.s, page_slug)
|
||||
if post and post.is_page:
|
||||
page_post_id = post.id
|
||||
|
||||
# --- product cart ---
|
||||
@@ -73,26 +68,19 @@ def register() -> Blueprint:
|
||||
if ci.product and (ci.product.special_price or ci.product.regular_price)
|
||||
)
|
||||
|
||||
# --- calendar entries ---
|
||||
cal_q = select(CalendarEntry).where(
|
||||
CalendarEntry.deleted_at.is_(None),
|
||||
CalendarEntry.state == "pending",
|
||||
)
|
||||
if ident["user_id"] is not None:
|
||||
cal_q = cal_q.where(CalendarEntry.user_id == ident["user_id"])
|
||||
else:
|
||||
cal_q = cal_q.where(CalendarEntry.session_id == ident["session_id"])
|
||||
|
||||
# --- calendar entries via service ---
|
||||
if page_post_id is not None:
|
||||
cal_ids = select(Calendar.id).where(
|
||||
Calendar.container_type == "page",
|
||||
Calendar.container_id == page_post_id,
|
||||
Calendar.deleted_at.is_(None),
|
||||
).scalar_subquery()
|
||||
cal_q = cal_q.where(CalendarEntry.calendar_id.in_(cal_ids))
|
||||
|
||||
cal_result = await g.s.execute(cal_q)
|
||||
cal_entries = cal_result.scalars().all()
|
||||
cal_entries = await services.calendar.entries_for_page(
|
||||
g.s, page_post_id,
|
||||
user_id=ident["user_id"],
|
||||
session_id=ident["session_id"],
|
||||
)
|
||||
else:
|
||||
cal_entries = await services.calendar.pending_entries(
|
||||
g.s,
|
||||
user_id=ident["user_id"],
|
||||
session_id=ident["session_id"],
|
||||
)
|
||||
|
||||
calendar_count = len(cal_entries)
|
||||
calendar_total = sum((e.cost or 0) for e in cal_entries if e.cost is not None)
|
||||
|
||||
@@ -6,9 +6,8 @@ from quart import Blueprint, g, request, render_template, redirect, url_for, mak
|
||||
from sqlalchemy import select
|
||||
|
||||
from shared.models.order import Order
|
||||
from shared.models.ghost_content import Post
|
||||
from shared.models.market_place import MarketPlace
|
||||
from glue.services.order_lifecycle import get_entries_for_order
|
||||
from shared.services.registry import services
|
||||
from .services import (
|
||||
current_cart_identity,
|
||||
get_cart,
|
||||
@@ -182,7 +181,7 @@ def register(url_prefix: str) -> Blueprint:
|
||||
|
||||
# Resolve page/market slugs so product links render correctly
|
||||
if order.page_config:
|
||||
post = await g.s.get(Post, order.page_config.container_id)
|
||||
post = await services.blog.get_post_by_id(g.s, order.page_config.container_id)
|
||||
if post:
|
||||
g.page_slug = post.slug
|
||||
result = await g.s.execute(
|
||||
@@ -204,7 +203,7 @@ def register(url_prefix: str) -> Blueprint:
|
||||
|
||||
status = (order.status or "pending").lower()
|
||||
|
||||
calendar_entries = await get_entries_for_order(g.s, order.id)
|
||||
calendar_entries = await services.calendar.get_entries_for_order(g.s, order.id)
|
||||
await g.s.flush()
|
||||
|
||||
html = await render_template(
|
||||
|
||||
@@ -1,38 +1,20 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from shared.models.calendars import CalendarEntry
|
||||
from shared.services.registry import services
|
||||
from .identity import current_cart_identity
|
||||
|
||||
|
||||
async def get_calendar_cart_entries(session):
|
||||
"""
|
||||
Return all *pending* calendar entries for the current cart identity
|
||||
(user or anonymous session).
|
||||
Return all *pending* calendar entries (as CalendarEntryDTOs) for the
|
||||
current cart identity (user or anonymous session).
|
||||
"""
|
||||
ident = current_cart_identity()
|
||||
|
||||
filters = [
|
||||
CalendarEntry.deleted_at.is_(None),
|
||||
CalendarEntry.state == "pending",
|
||||
]
|
||||
|
||||
if ident["user_id"] is not None:
|
||||
filters.append(CalendarEntry.user_id == ident["user_id"])
|
||||
else:
|
||||
filters.append(CalendarEntry.session_id == ident["session_id"])
|
||||
|
||||
result = await session.execute(
|
||||
select(CalendarEntry)
|
||||
.where(*filters)
|
||||
.order_by(CalendarEntry.start_at.asc())
|
||||
.options(
|
||||
selectinload(CalendarEntry.calendar),
|
||||
)
|
||||
return await services.calendar.pending_entries(
|
||||
session,
|
||||
user_id=ident["user_id"],
|
||||
session_id=ident["session_id"],
|
||||
)
|
||||
return result.scalars().all()
|
||||
|
||||
|
||||
def calendar_total(entries) -> float:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from shared.browser.app.payments.sumup import get_checkout as sumup_get_checkout
|
||||
from shared.events import emit_event
|
||||
from glue.services.order_lifecycle import confirm_entries_for_order
|
||||
from shared.services.registry import services
|
||||
|
||||
|
||||
async def check_sumup_status(session, order):
|
||||
@@ -13,7 +13,7 @@ async def check_sumup_status(session, order):
|
||||
if sumup_status == "PAID":
|
||||
if order.status != "paid":
|
||||
order.status = "paid"
|
||||
await confirm_entries_for_order(
|
||||
await services.calendar.confirm_entries_for_order(
|
||||
session, order.id, order.user_id, order.session_id
|
||||
)
|
||||
await emit_event(session, "order.paid", "order", order.id, {
|
||||
|
||||
@@ -9,12 +9,12 @@ from sqlalchemy.orm import selectinload
|
||||
|
||||
from shared.models.market import Product, CartItem
|
||||
from shared.models.order import Order, OrderItem
|
||||
from shared.models.calendars import CalendarEntry, Calendar
|
||||
from shared.models.calendars import Calendar
|
||||
from shared.models.page_config import PageConfig
|
||||
from shared.models.market_place import MarketPlace
|
||||
from shared.config import config
|
||||
from shared.events import emit_event
|
||||
from glue.services.order_lifecycle import claim_entries_for_order
|
||||
from shared.services.registry import services
|
||||
|
||||
|
||||
async def find_or_create_cart_item(
|
||||
@@ -150,8 +150,8 @@ async def create_order_from_cart(
|
||||
)
|
||||
session.add(oi)
|
||||
|
||||
# Mark pending calendar entries as "ordered" via glue service
|
||||
await claim_entries_for_order(
|
||||
# Mark pending calendar entries as "ordered" via calendar service
|
||||
await services.calendar.claim_entries_for_order(
|
||||
session, order.id, user_id, session_id, page_post_id
|
||||
)
|
||||
|
||||
|
||||
@@ -14,9 +14,8 @@ from sqlalchemy.orm import selectinload
|
||||
|
||||
from shared.models.market import CartItem
|
||||
from shared.models.market_place import MarketPlace
|
||||
from shared.models.calendars import CalendarEntry, Calendar
|
||||
from shared.models.ghost_content import Post
|
||||
from shared.models.page_config import PageConfig
|
||||
from shared.services.registry import services
|
||||
from .identity import current_cart_identity
|
||||
|
||||
|
||||
@@ -48,30 +47,14 @@ async def get_cart_for_page(session, post_id: int) -> list[CartItem]:
|
||||
return result.scalars().all()
|
||||
|
||||
|
||||
async def get_calendar_entries_for_page(session, post_id: int) -> list[CalendarEntry]:
|
||||
"""Return pending calendar entries scoped to a specific page (via Calendar.container_id)."""
|
||||
async def get_calendar_entries_for_page(session, post_id: int):
|
||||
"""Return pending calendar entries (DTOs) scoped to a specific page."""
|
||||
ident = current_cart_identity()
|
||||
|
||||
filters = [
|
||||
CalendarEntry.deleted_at.is_(None),
|
||||
CalendarEntry.state == "pending",
|
||||
Calendar.container_type == "page",
|
||||
Calendar.container_id == post_id,
|
||||
Calendar.deleted_at.is_(None),
|
||||
]
|
||||
if ident["user_id"] is not None:
|
||||
filters.append(CalendarEntry.user_id == ident["user_id"])
|
||||
else:
|
||||
filters.append(CalendarEntry.session_id == ident["session_id"])
|
||||
|
||||
result = await session.execute(
|
||||
select(CalendarEntry)
|
||||
.join(Calendar, CalendarEntry.calendar_id == Calendar.id)
|
||||
.where(*filters)
|
||||
.order_by(CalendarEntry.start_at.asc())
|
||||
.options(selectinload(CalendarEntry.calendar))
|
||||
return await services.calendar.entries_for_page(
|
||||
session, post_id,
|
||||
user_id=ident["user_id"],
|
||||
session_id=ident["session_id"],
|
||||
)
|
||||
return result.scalars().all()
|
||||
|
||||
|
||||
async def get_cart_grouped_by_page(session) -> list[dict]:
|
||||
@@ -125,16 +108,13 @@ async def get_cart_grouped_by_page(session) -> list[dict]:
|
||||
groups[pid]["post_id"] = pid
|
||||
groups[pid]["calendar_entries"].append(ce)
|
||||
|
||||
# Batch-load Post and PageConfig objects
|
||||
# Batch-load Post DTOs and PageConfig objects
|
||||
post_ids = [pid for pid in groups if pid is not None]
|
||||
posts_by_id: dict[int, Post] = {}
|
||||
posts_by_id: dict[int, object] = {}
|
||||
configs_by_post: dict[int, PageConfig] = {}
|
||||
|
||||
if post_ids:
|
||||
post_result = await session.execute(
|
||||
select(Post).where(Post.id.in_(post_ids))
|
||||
)
|
||||
for p in post_result.scalars().all():
|
||||
for p in await services.blog.get_posts_by_ids(session, post_ids):
|
||||
posts_by_id[p.id] = p
|
||||
|
||||
pc_result = await session.execute(
|
||||
|
||||
25
services/__init__.py
Normal file
25
services/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""Cart app service registration."""
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def register_domain_services() -> None:
|
||||
"""Register services for the cart app.
|
||||
|
||||
Cart owns: Order, OrderItem.
|
||||
Standard deployment registers all 4 services as real DB impls
|
||||
(shared DB). For composable deployments, swap non-owned services
|
||||
with stubs from shared.services.stubs.
|
||||
"""
|
||||
from shared.services.registry import services
|
||||
from shared.services.blog_impl import SqlBlogService
|
||||
from shared.services.calendar_impl import SqlCalendarService
|
||||
from shared.services.market_impl import SqlMarketService
|
||||
from shared.services.cart_impl import SqlCartService
|
||||
|
||||
services.cart = SqlCartService()
|
||||
if not services.has("blog"):
|
||||
services.blog = SqlBlogService()
|
||||
if not services.has("calendar"):
|
||||
services.calendar = SqlCalendarService()
|
||||
if not services.has("market"):
|
||||
services.market = SqlMarketService()
|
||||
2
shared
2
shared
Submodule shared updated: ea7dc9723a...70b1c7de10
Reference in New Issue
Block a user