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

@@ -0,0 +1,48 @@
"""Denormalize product and marketplace data onto cart_items and order_items.
Revision ID: cart_0002
Revises: cart_0001
Create Date: 2026-02-26
"""
import sqlalchemy as sa
from alembic import op
revision = "cart_0002"
down_revision = "cart_0001"
branch_labels = None
depends_on = None
def upgrade():
# -- cart_items: denormalized product data --
op.add_column("cart_items", sa.Column("product_title", sa.String(512), nullable=True))
op.add_column("cart_items", sa.Column("product_slug", sa.String(512), nullable=True))
op.add_column("cart_items", sa.Column("product_image", sa.Text, nullable=True))
op.add_column("cart_items", sa.Column("product_brand", sa.String(255), nullable=True))
op.add_column("cart_items", sa.Column("product_regular_price", sa.Numeric(12, 2), nullable=True))
op.add_column("cart_items", sa.Column("product_special_price", sa.Numeric(12, 2), nullable=True))
op.add_column("cart_items", sa.Column("product_price_currency", sa.String(16), nullable=True))
# -- cart_items: denormalized marketplace data --
op.add_column("cart_items", sa.Column("market_place_name", sa.String(255), nullable=True))
op.add_column("cart_items", sa.Column("market_place_container_id", sa.Integer, nullable=True))
# -- order_items: denormalized product fields --
op.add_column("order_items", sa.Column("product_slug", sa.String(512), nullable=True))
op.add_column("order_items", sa.Column("product_image", sa.Text, nullable=True))
def downgrade():
op.drop_column("order_items", "product_image")
op.drop_column("order_items", "product_slug")
op.drop_column("cart_items", "market_place_container_id")
op.drop_column("cart_items", "market_place_name")
op.drop_column("cart_items", "product_price_currency")
op.drop_column("cart_items", "product_special_price")
op.drop_column("cart_items", "product_regular_price")
op.drop_column("cart_items", "product_brand")
op.drop_column("cart_items", "product_image")
op.drop_column("cart_items", "product_slug")
op.drop_column("cart_items", "product_title")