diff --git a/contracts/protocols.py b/contracts/protocols.py index 2eecbc5..d6a0574 100644 --- a/contracts/protocols.py +++ b/contracts/protocols.py @@ -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): diff --git a/services/market_impl.py b/services/market_impl.py index f99525a..5f991ec 100644 --- a/services/market_impl.py +++ b/services/market_impl.py @@ -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 diff --git a/services/stubs.py b/services/stubs.py index 96c917c..4502cc4 100644 --- a/services/stubs.py +++ b/services/stubs.py @@ -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(