This repository has been archived on 2026-02-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
market/bp/product/services/product_operations.py
giles 478636f799 feat: decouple market from shared_lib, add app-owned models
Phase 1-3 of decoupling:
- path_setup.py adds project root to sys.path
- Market-owned models in market/models/ (market, market_place)
- All imports updated: shared.infrastructure, shared.db, shared.browser, etc.
- MarketPlace uses container_type/container_id instead of post_id FK

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 12:46:32 +00:00

96 lines
3.1 KiB
Python

from __future__ import annotations
from typing import Optional
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from models.market import Product, ProductLike
def massage_full_product(product: Product) -> dict:
"""
Convert a Product ORM model to a dictionary with all fields.
Used for rendering product detail pages.
"""
from bp.browse.services import _massage_product
gallery = []
if product.image:
gallery.append(product.image)
d = {
"id": product.id,
"slug": product.slug,
"title": product.title,
"brand": product.brand,
"image": product.image,
"description_short": product.description_short,
"description_html": product.description_html or "",
"suma_href": product.suma_href,
"rrp": float(product.rrp) if product.rrp else None,
"special_price": float(product.special_price) if product.special_price else None,
"regular_price": float(product.regular_price) if product.regular_price else None,
"images": gallery or [img.url for img in product.images],
"all_image_urls": gallery or [img.url for img in product.images],
"sections": [{"title": s.title, "html": s.html} for s in product.sections],
"stickers": [s.name.lower() for s in product.stickers],
"labels": [l.name for l in product.labels],
"nutrition": [{"key": n.key, "value": n.value, "unit": n.unit} for n in product.nutrition],
"allergens": [{"name": a.name, "contains": a.contains} for a in product.allergens],
"is_liked": False,
}
return _massage_product(d)
async def toggle_product_like(
session: AsyncSession,
user_id: int,
product_slug: str,
) -> tuple[bool, Optional[str]]:
"""
Toggle a product like for a given user using soft deletes.
Returns (liked_state, error_message).
- If error_message is not None, an error occurred.
- liked_state indicates whether product is now liked (True) or unliked (False).
"""
from sqlalchemy import func, update
# Get product_id from slug
product_id = await session.scalar(
select(Product.id).where(Product.slug == product_slug, Product.deleted_at.is_(None))
)
if not product_id:
return False, "Product not found"
# Check if like exists (not deleted)
existing = await session.scalar(
select(ProductLike).where(
ProductLike.user_id == user_id,
ProductLike.product_slug == product_slug,
ProductLike.deleted_at.is_(None),
)
)
if existing:
# Unlike: soft delete the like
await session.execute(
update(ProductLike)
.where(
ProductLike.user_id == user_id,
ProductLike.product_slug == product_slug,
ProductLike.deleted_at.is_(None),
)
.values(deleted_at=func.now())
)
return False, None
else:
# Like: add a new like
new_like = ProductLike(
user_id=user_id,
product_slug=product_slug,
)
session.add(new_like)
return True, None