Domain isolation: replace cross-domain imports with service calls
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 50s

Replace direct Calendar, MarketPlace, and Post model queries with typed
service calls (services.blog, services.calendar, services.market,
services.cart). Blog registers all 4 services via domain_services_fn
with has() guards for composable deployment.

Key changes:
- app.py: use domain_services_fn instead of inline service registration
- admin routes: MarketPlace queries → services.market.marketplaces_for_container()
- entry_associations: CalendarEntryPost → services.calendar.entry_ids_for_content()
- markets service: Post query → services.blog.get_post_by_id/slug()
- posts_data, post routes: use calendar/market/cart services
- menu_items: glue imports → shared imports

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
giles
2026-02-19 04:30:14 +00:00
parent e1f4471002
commit 4155df7e7c
9 changed files with 89 additions and 203 deletions

View File

@@ -1,11 +1,8 @@
from __future__ import annotations
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from sqlalchemy.sql import func
from shared.models.calendars import CalendarEntry, CalendarEntryPost, Calendar
from models.ghost_content import Post
from shared.services.registry import services
async def toggle_entry_association(
@@ -17,45 +14,14 @@ async def toggle_entry_association(
Toggle association between a post and calendar entry.
Returns (is_now_associated, error_message).
"""
# Check if entry exists (don't filter by deleted_at - allow associating with any entry)
entry = await session.scalar(
select(CalendarEntry).where(CalendarEntry.id == entry_id)
)
if not entry:
return False, f"Calendar entry {entry_id} not found in database"
# Check if post exists
post = await session.scalar(
select(Post).where(Post.id == post_id)
)
post = await services.blog.get_post_by_id(session, post_id)
if not post:
return False, "Post not found"
# Check if association already exists
existing = await session.scalar(
select(CalendarEntryPost).where(
CalendarEntryPost.entry_id == entry_id,
CalendarEntryPost.content_type == "post",
CalendarEntryPost.content_id == post_id,
CalendarEntryPost.deleted_at.is_(None)
)
is_associated = await services.calendar.toggle_entry_post(
session, entry_id, "post", post_id,
)
if existing:
# Remove association (soft delete)
existing.deleted_at = func.now()
await session.flush()
return False, None
else:
# Create association
association = CalendarEntryPost(
entry_id=entry_id,
content_type="post",
content_id=post_id
)
session.add(association)
await session.flush()
return True, None
return is_associated, None
async def get_post_entry_ids(
@@ -66,15 +32,7 @@ async def get_post_entry_ids(
Get all entry IDs associated with this post.
Returns a set of entry IDs.
"""
result = await session.execute(
select(CalendarEntryPost.entry_id)
.where(
CalendarEntryPost.content_type == "post",
CalendarEntryPost.content_id == post_id,
CalendarEntryPost.deleted_at.is_(None)
)
)
return set(result.scalars().all())
return await services.calendar.entry_ids_for_content(session, "post", post_id)
async def get_associated_entries(
@@ -85,59 +43,14 @@ async def get_associated_entries(
) -> dict:
"""
Get paginated associated entries for this post.
Returns dict with entries, total_count, and has_more.
Returns dict with entries (CalendarEntryDTOs), total_count, and has_more.
"""
# Get all associated entry IDs
entry_ids_result = await session.execute(
select(CalendarEntryPost.entry_id)
.where(
CalendarEntryPost.content_type == "post",
CalendarEntryPost.content_id == post_id,
CalendarEntryPost.deleted_at.is_(None)
)
entries, has_more = await services.calendar.associated_entries(
session, "post", post_id, page,
)
entry_ids = set(entry_ids_result.scalars().all())
if not entry_ids:
return {
"entries": [],
"total_count": 0,
"has_more": False,
"page": page,
}
# Get total count
from sqlalchemy import func
total_count = len(entry_ids)
# Get paginated entries ordered by start_at desc
# Only include confirmed entries
offset = (page - 1) * per_page
result = await session.execute(
select(CalendarEntry)
.where(
CalendarEntry.id.in_(entry_ids),
CalendarEntry.deleted_at.is_(None),
CalendarEntry.state == "confirmed" # Only confirmed entries in nav
)
.order_by(CalendarEntry.start_at.desc())
.limit(per_page)
.offset(offset)
)
entries = result.scalars().all()
# Recalculate total_count based on confirmed entries only
total_count = len(entries) + offset # Rough estimate
if len(entries) < per_page:
total_count = offset + len(entries)
# Load calendar relationship for each entry
for entry in entries:
await session.refresh(entry, ["calendar"])
if entry.calendar:
await session.refresh(entry.calendar, ["post"])
has_more = len(entries) == per_page # More accurate check
total_count = len(entries) + (page - 1) * per_page
if has_more:
total_count += 1 # at least one more
return {
"entries": entries,

View File

@@ -7,10 +7,10 @@ from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from shared.models.market_place import MarketPlace
from models.ghost_content import Post
from shared.models.page_config import PageConfig
from shared.browser.app.utils import utcnow
from glue.services.relationships import attach_child, detach_child
from shared.services.registry import services
from shared.services.relationships import attach_child, detach_child
class MarketError(ValueError):
@@ -36,7 +36,7 @@ async def create_market(sess: AsyncSession, post_id: int, name: str) -> MarketPl
raise MarketError("Market name must not be empty.")
slug = slugify(name)
post = (await sess.execute(select(Post).where(Post.id == post_id))).scalar_one_or_none()
post = await services.blog.get_post_by_id(sess, post_id)
if not post:
raise MarketError(f"Post {post_id} does not exist.")
@@ -71,13 +71,16 @@ async def create_market(sess: AsyncSession, post_id: int, name: str) -> MarketPl
async def soft_delete_market(sess: AsyncSession, post_slug: str, market_slug: str) -> bool:
post = await services.blog.get_post_by_slug(sess, post_slug)
if not post:
return False
market = (
await sess.execute(
select(MarketPlace)
.join(Post, MarketPlace.container_id == Post.id)
.where(MarketPlace.container_type == "page")
.where(
Post.slug == post_slug,
MarketPlace.container_type == "page",
MarketPlace.container_id == post.id,
MarketPlace.slug == market_slug,
MarketPlace.deleted_at.is_(None),
)