Decouple cart/market DBs: denormalize product data, AP internal inbox, OAuth scraper auth

Remove cross-DB relationships (CartItem.product, CartItem.market_place,
OrderItem.product) that break with per-service databases. Denormalize
product and marketplace fields onto cart_items/order_items at write time.

- Add AP internal inbox infrastructure (shared/infrastructure/internal_inbox*)
  for synchronous inter-service writes via HMAC-authenticated POST
- Cart inbox blueprint handles Add/Remove/Update rose:CartItem activities
- Market app sends AP activities to cart inbox instead of writing CartItem directly
- Cart services use denormalized columns instead of cross-DB hydration/joins
- Add marketplaces-by-ids data endpoint to market service
- Alembic migration adds denormalized columns to cart_items and order_items
- Add OAuth device flow auth to market scraper persist_api (artdag client pattern)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-26 14:49:04 +00:00
parent cf7fbd8e9b
commit 81112c716b
28 changed files with 739 additions and 186 deletions

View File

@@ -76,4 +76,35 @@ def register() -> Blueprint:
_handlers["products-by-ids"] = _products_by_ids
# --- marketplaces-by-ids ---
async def _marketplaces_by_ids():
"""Return marketplace data for a list of IDs (comma-separated)."""
from sqlalchemy import select
from shared.models.market_place import MarketPlace
ids_raw = request.args.get("ids", "")
try:
ids = [int(x) for x in ids_raw.split(",") if x.strip()]
except ValueError:
return {"error": "ids must be comma-separated integers"}, 400
if not ids:
return []
rows = (await g.s.execute(
select(MarketPlace).where(MarketPlace.id.in_(ids))
)).scalars().all()
return [
{
"id": m.id,
"name": m.name,
"slug": m.slug,
"container_type": m.container_type,
"container_id": m.container_id,
}
for m in rows
]
_handlers["marketplaces-by-ids"] = _marketplaces_by_ids
return bp