Add MarketService write methods and clean up stubs

Add create_marketplace() and soft_delete_marketplace() to MarketService
protocol, SQL implementation, and stubs — centralises market CRUD that
was previously duplicated in blog and events app-level service files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
giles
2026-02-19 05:43:55 +00:00
parent 9cba422aa9
commit b3a0e9922a
3 changed files with 76 additions and 0 deletions

View File

@@ -78,6 +78,16 @@ class MarketService(Protocol):
async def product_by_id(self, session: AsyncSession, product_id: int) -> ProductDTO | None: ...
async def create_marketplace(
self, session: AsyncSession, container_type: str, container_id: int,
name: str, slug: str,
) -> MarketPlaceDTO: ...
async def soft_delete_marketplace(
self, session: AsyncSession, container_type: str, container_id: int,
slug: str,
) -> bool: ...
@runtime_checkable
class CartService(Protocol):

View File

@@ -10,7 +10,9 @@ from sqlalchemy.ext.asyncio import AsyncSession
from shared.models.market import Product
from shared.models.market_place import MarketPlace
from shared.browser.app.utils import utcnow
from shared.contracts.dtos import MarketPlaceDTO, ProductDTO
from shared.services.relationships import attach_child, detach_child
def _mp_to_dto(mp: MarketPlace) -> MarketPlaceDTO:
@@ -55,3 +57,55 @@ class SqlMarketService:
await session.execute(select(Product).where(Product.id == product_id))
).scalar_one_or_none()
return _product_to_dto(product) if product else None
async def create_marketplace(
self, session: AsyncSession, container_type: str, container_id: int,
name: str, slug: str,
) -> MarketPlaceDTO:
# Look for existing (including soft-deleted)
existing = (await session.execute(
select(MarketPlace).where(
MarketPlace.container_type == container_type,
MarketPlace.container_id == container_id,
MarketPlace.slug == slug,
)
)).scalar_one_or_none()
if existing:
if existing.deleted_at is not None:
existing.deleted_at = None # revive
existing.name = name
await session.flush()
await attach_child(session, container_type, container_id, "market", existing.id)
return _mp_to_dto(existing)
raise ValueError(f'Market with slug "{slug}" already exists for this container.')
market = MarketPlace(
container_type=container_type, container_id=container_id,
name=name, slug=slug,
)
session.add(market)
await session.flush()
await attach_child(session, container_type, container_id, "market", market.id)
return _mp_to_dto(market)
async def soft_delete_marketplace(
self, session: AsyncSession, container_type: str, container_id: int,
slug: str,
) -> bool:
market = (await session.execute(
select(MarketPlace).where(
MarketPlace.container_type == container_type,
MarketPlace.container_id == container_id,
MarketPlace.slug == slug,
MarketPlace.deleted_at.is_(None),
)
)).scalar_one_or_none()
if not market:
return False
market.deleted_at = utcnow()
await session.flush()
await detach_child(session, container_type, container_id, "market", market.id)
return True

View File

@@ -92,6 +92,18 @@ class StubMarketService:
async def product_by_id(self, session: AsyncSession, product_id: int) -> ProductDTO | None:
return None
async def create_marketplace(
self, session: AsyncSession, container_type: str, container_id: int,
name: str, slug: str,
) -> MarketPlaceDTO:
raise RuntimeError("MarketService not available")
async def soft_delete_marketplace(
self, session: AsyncSession, container_type: str, container_id: int,
slug: str,
) -> bool:
return False
class StubCartService:
async def cart_summary(