Split databases and Redis — prepare infrastructure for per-domain isolation
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m20s

Redis: per-app DB index (0-5) with shared auth DB 15 for SSO keys;
flushdb replaces flushall so deploys don't wipe cross-app auth state.

Postgres: drop 13 cross-domain FK constraints (migration v2t0p8q9r0),
remove dead ORM relationships, add explicit joins for 4 live ones.
Multi-engine sessions (account + federation) ready for per-domain DBs
via DATABASE_URL_ACCOUNT / DATABASE_URL_FEDERATION env vars.

All URLs initially point to the same appdb — zero behaviour change
until split-databases.sh is run to migrate data to per-domain DBs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
giles
2026-02-25 02:20:34 +00:00
parent 57d2a6a6e3
commit 580f551700
25 changed files with 459 additions and 102 deletions

View File

@@ -112,18 +112,8 @@ class Product(Base):
back_populates="product",
cascade="all, delete-orphan",
)
cart_items: Mapped[List["CartItem"]] = relationship(
"CartItem",
back_populates="product",
cascade="all, delete-orphan",
)
# NEW: all order items that reference this product
order_items: Mapped[List["OrderItem"]] = relationship(
"OrderItem",
back_populates="product",
cascade="all, delete-orphan",
)
# cart_items and order_items live in a separate domain (cart DB)
# — cross-domain relationships removed
from sqlalchemy import Column
@@ -131,7 +121,7 @@ class ProductLike(Base):
__tablename__ = "product_likes"
id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
user_id = Column(Integer, nullable=False)
product_slug: Mapped[str] = mapped_column(ForeignKey("products.slug", ondelete="CASCADE"))
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
@@ -139,8 +129,6 @@ class ProductLike(Base):
deleted_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
product: Mapped["Product"] = relationship("Product", back_populates="likes", foreign_keys=[product_slug])
user = relationship("User", back_populates="liked_products") # optional, if you want reverse access
class ProductImage(Base):
__tablename__ = "product_images"
@@ -381,7 +369,7 @@ class CartItem(Base):
# Either a logged-in user OR an anonymous session
user_id: Mapped[int | None] = mapped_column(
ForeignKey("users.id", ondelete="CASCADE"),
Integer,
nullable=True,
)
session_id: Mapped[str | None] = mapped_column(
@@ -389,9 +377,8 @@ class CartItem(Base):
nullable=True,
)
# IMPORTANT: link to product *id*, not slug
product_id: Mapped[int] = mapped_column(
ForeignKey("products.id", ondelete="CASCADE"),
Integer,
nullable=False,
)
@@ -413,7 +400,7 @@ class CartItem(Base):
server_default=func.now(),
)
market_place_id: Mapped[int | None] = mapped_column(
ForeignKey("market_places.id", ondelete="SET NULL"),
Integer,
nullable=True,
index=True,
)
@@ -423,17 +410,19 @@ class CartItem(Base):
nullable=True,
)
# Relationships
# Cross-domain relationships — explicit join, viewonly (no FK constraint)
market_place: Mapped["MarketPlace | None"] = relationship(
"MarketPlace",
foreign_keys=[market_place_id],
primaryjoin="CartItem.market_place_id == MarketPlace.id",
foreign_keys="[CartItem.market_place_id]",
viewonly=True,
)
product: Mapped["Product"] = relationship(
"Product",
back_populates="cart_items",
primaryjoin="CartItem.product_id == Product.id",
foreign_keys="[CartItem.product_id]",
viewonly=True,
)
user: Mapped["User | None"] = relationship("User", back_populates="cart_items")
__table_args__ = (
Index("ix_cart_items_user_product", "user_id", "product_id"),