"""Tree — the top-level tenant boundary for genealogical data — and TreeMembership, the basis for authorization (ARCHITECTURE §5). """ import uuid from sqlalchemy import Enum as SAEnum from sqlalchemy import ForeignKey, String, Text, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column from app.models.base import Base from app.models.enums import MembershipRole, TreeVisibility from app.models.mixins import SoftDelete, Timestamps, UUIDPrimaryKey class Tree(Base, UUIDPrimaryKey, Timestamps, SoftDelete): __tablename__ = "trees" owner_id: Mapped[uuid.UUID] = mapped_column( ForeignKey("users.id", ondelete="RESTRICT"), index=True ) name: Mapped[str] = mapped_column(String(255)) description: Mapped[str | None] = mapped_column(Text) visibility: Mapped[TreeVisibility] = mapped_column( SAEnum(TreeVisibility, name="tree_visibility"), default=TreeVisibility.private, server_default=TreeVisibility.private.value, ) # The person a tree opens focused on (its "home"/root person). Cleared if # that person is deleted. use_alter + name: trees<->persons form an FK cycle. home_person_id: Mapped[uuid.UUID | None] = mapped_column( ForeignKey( "persons.id", ondelete="SET NULL", name="fk_trees_home_person_id", use_alter=True, ) ) class TreeMembership(Base, UUIDPrimaryKey, Timestamps): __tablename__ = "tree_memberships" __table_args__ = ( UniqueConstraint("tree_id", "user_id", name="uq_tree_memberships_tree_user"), ) tree_id: Mapped[uuid.UUID] = mapped_column( ForeignKey("trees.id", ondelete="CASCADE"), index=True ) user_id: Mapped[uuid.UUID] = mapped_column( ForeignKey("users.id", ondelete="CASCADE"), index=True ) role: Mapped[MembershipRole] = mapped_column(SAEnum(MembershipRole, name="membership_role"))