Split databases and Redis — prepare infrastructure for per-domain isolation
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:
@@ -77,8 +77,8 @@ class CalendarEntry(Base):
|
||||
index=True,
|
||||
)
|
||||
|
||||
# NEW: ownership + order link
|
||||
user_id = Column(Integer, ForeignKey("users.id"), nullable=True, index=True)
|
||||
# Ownership (cross-domain — no FK constraint to users table)
|
||||
user_id = Column(Integer, nullable=True, index=True)
|
||||
session_id = Column(String(64), nullable=True, index=True)
|
||||
order_id = Column(Integer, nullable=True, index=True)
|
||||
|
||||
@@ -246,7 +246,7 @@ class Ticket(Base):
|
||||
nullable=True,
|
||||
index=True,
|
||||
)
|
||||
user_id = Column(Integer, ForeignKey("users.id"), nullable=True, index=True)
|
||||
user_id = Column(Integer, nullable=True, index=True)
|
||||
session_id = Column(String(64), nullable=True, index=True)
|
||||
order_id = Column(Integer, nullable=True, index=True)
|
||||
|
||||
|
||||
@@ -23,8 +23,7 @@ class ActorProfile(Base):
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
user_id: Mapped[int] = mapped_column(
|
||||
Integer, ForeignKey("users.id", ondelete="CASCADE"),
|
||||
unique=True, nullable=False,
|
||||
Integer, unique=True, nullable=False,
|
||||
)
|
||||
preferred_username: Mapped[str] = mapped_column(String(64), unique=True, nullable=False)
|
||||
display_name: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||||
@@ -36,7 +35,6 @@ class ActorProfile(Base):
|
||||
)
|
||||
|
||||
# Relationships
|
||||
user = relationship("User", backref="actor_profile", uselist=False, lazy="selectin")
|
||||
activities = relationship("APActivity", back_populates="actor_profile", lazy="dynamic")
|
||||
followers = relationship("APFollower", back_populates="actor_profile", lazy="dynamic")
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ class Post(Base):
|
||||
deleted_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
||||
|
||||
user_id: Mapped[Optional[int]] = mapped_column(
|
||||
Integer, ForeignKey("users.id", ondelete="SET NULL"), index=True
|
||||
Integer, index=True
|
||||
)
|
||||
publish_requested: Mapped[bool] = mapped_column(Boolean(), default=False, server_default="false", nullable=False)
|
||||
|
||||
@@ -111,9 +111,6 @@ class Post(Base):
|
||||
primary_tag: Mapped[Optional[Tag]] = relationship(
|
||||
"Tag", foreign_keys=[primary_tag_id]
|
||||
)
|
||||
user: Mapped[Optional["User"]] = relationship(
|
||||
"User", foreign_keys=[user_id]
|
||||
)
|
||||
|
||||
# AUTHORS RELATIONSHIP (many-to-many via post_authors)
|
||||
authors: Mapped[List["Author"]] = relationship(
|
||||
@@ -205,7 +202,7 @@ class PostLike(Base):
|
||||
__tablename__ = "post_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)
|
||||
post_id: Mapped[int] = mapped_column(ForeignKey("posts.id", ondelete="CASCADE"), nullable=False)
|
||||
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
@@ -213,4 +210,3 @@ class PostLike(Base):
|
||||
deleted_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
||||
|
||||
post: Mapped["Post"] = relationship("Post", back_populates="likes", foreign_keys=[post_id])
|
||||
user = relationship("User", back_populates="liked_posts")
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -13,7 +13,6 @@ class MenuItem(Base):
|
||||
|
||||
post_id: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
ForeignKey("posts.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
index=True
|
||||
)
|
||||
|
||||
@@ -14,11 +14,11 @@ class Order(Base):
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
|
||||
user_id: Mapped[Optional[int]] = mapped_column(ForeignKey("users.id"), nullable=True)
|
||||
user_id: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
|
||||
session_id: Mapped[Optional[str]] = mapped_column(String(64), index=True, nullable=True)
|
||||
|
||||
page_config_id: Mapped[Optional[int]] = mapped_column(
|
||||
ForeignKey("page_configs.id", ondelete="SET NULL"),
|
||||
Integer,
|
||||
nullable=True,
|
||||
index=True,
|
||||
)
|
||||
@@ -69,9 +69,12 @@ class Order(Base):
|
||||
cascade="all, delete-orphan",
|
||||
lazy="selectin",
|
||||
)
|
||||
# Cross-domain relationship — explicit join, viewonly (no FK constraint)
|
||||
page_config: Mapped[Optional["PageConfig"]] = relationship(
|
||||
"PageConfig",
|
||||
foreign_keys=[page_config_id],
|
||||
primaryjoin="Order.page_config_id == PageConfig.id",
|
||||
foreign_keys="[Order.page_config_id]",
|
||||
viewonly=True,
|
||||
lazy="selectin",
|
||||
)
|
||||
|
||||
@@ -86,7 +89,7 @@ class OrderItem(Base):
|
||||
)
|
||||
|
||||
product_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("products.id"),
|
||||
Integer,
|
||||
nullable=False,
|
||||
)
|
||||
product_title: Mapped[Optional[str]] = mapped_column(String(512), nullable=True)
|
||||
@@ -106,9 +109,11 @@ class OrderItem(Base):
|
||||
back_populates="items",
|
||||
)
|
||||
|
||||
# NEW: link each order item to its product
|
||||
# Cross-domain relationship — explicit join, viewonly (no FK constraint)
|
||||
product: Mapped["Product"] = relationship(
|
||||
"Product",
|
||||
back_populates="order_items",
|
||||
primaryjoin="OrderItem.product_id == Product.id",
|
||||
foreign_keys="[OrderItem.product_id]",
|
||||
viewonly=True,
|
||||
lazy="selectin",
|
||||
)
|
||||
|
||||
@@ -30,13 +30,8 @@ class User(Base):
|
||||
labels = relationship("GhostLabel", secondary="user_labels", back_populates="users", lazy="selectin")
|
||||
subscriptions = relationship("GhostSubscription", back_populates="user", cascade="all, delete-orphan", lazy="selectin")
|
||||
|
||||
liked_products = relationship("ProductLike", back_populates="user", cascade="all, delete-orphan")
|
||||
liked_posts = relationship("PostLike", back_populates="user", cascade="all, delete-orphan")
|
||||
cart_items = relationship(
|
||||
"CartItem",
|
||||
back_populates="user",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
# Cross-domain reverse relationships removed (liked_products, liked_posts,
|
||||
# cart_items) — those tables live in different domain DBs
|
||||
|
||||
__table_args__ = (
|
||||
Index("ix_user_email", "email", unique=True),
|
||||
|
||||
Reference in New Issue
Block a user