# app/models.py import enum import secrets from datetime import datetime, timedelta, timezone from sqlalchemy import ( Column, Integer, String, DateTime, ForeignKey, Boolean, Enum as SAEnum, UniqueConstraint, Index, DDL, event, delete, func, text as sa_text, Text, # <-- Add Text for description Numeric # <-- Add Numeric for price ) from sqlalchemy.orm import relationship from .database import Base # --- Enums --- class UserRoleEnum(enum.Enum): owner = "owner" member = "member" # --- User Model --- class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) email = Column(String, unique=True, index=True, nullable=False) password_hash = Column(String, nullable=False) name = Column(String, index=True, nullable=True) created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False) # --- Relationships --- created_groups = relationship("Group", back_populates="creator") group_associations = relationship("UserGroup", back_populates="user", cascade="all, delete-orphan") created_invites = relationship("Invite", back_populates="creator") # --- NEW Relationships for Lists/Items --- created_lists = relationship("List", foreign_keys="List.created_by_id", back_populates="creator") # Link List.created_by_id -> User added_items = relationship("Item", foreign_keys="Item.added_by_id", back_populates="added_by_user") # Link Item.added_by_id -> User completed_items = relationship("Item", foreign_keys="Item.completed_by_id", back_populates="completed_by_user") # Link Item.completed_by_id -> User # --- End NEW Relationships --- # --- Group Model --- class Group(Base): __tablename__ = "groups" id = Column(Integer, primary_key=True, index=True) name = Column(String, index=True, nullable=False) created_by_id = Column(Integer, ForeignKey("users.id"), nullable=False) created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False) # --- Relationships --- creator = relationship("User", back_populates="created_groups") member_associations = relationship("UserGroup", back_populates="group", cascade="all, delete-orphan") invites = relationship("Invite", back_populates="group", cascade="all, delete-orphan") # --- NEW Relationship for Lists --- lists = relationship("List", back_populates="group", cascade="all, delete-orphan") # Link List.group_id -> Group # --- End NEW Relationship --- # --- UserGroup Association Model --- class UserGroup(Base): __tablename__ = "user_groups" __table_args__ = (UniqueConstraint('user_id', 'group_id', name='uq_user_group'),) id = Column(Integer, primary_key=True, index=True) user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False) group_id = Column(Integer, ForeignKey("groups.id", ondelete="CASCADE"), nullable=False) role = Column(SAEnum(UserRoleEnum, name="userroleenum", create_type=True), nullable=False, default=UserRoleEnum.member) joined_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False) user = relationship("User", back_populates="group_associations") group = relationship("Group", back_populates="member_associations") # --- Invite Model --- class Invite(Base): __tablename__ = "invites" __table_args__ = ( Index('ix_invites_active_code', 'code', unique=True, postgresql_where=sa_text('is_active = true')), ) id = Column(Integer, primary_key=True, index=True) code = Column(String, unique=False, index=True, nullable=False, default=lambda: secrets.token_urlsafe(16)) group_id = Column(Integer, ForeignKey("groups.id", ondelete="CASCADE"), nullable=False) created_by_id = Column(Integer, ForeignKey("users.id"), nullable=False) created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False) expires_at = Column(DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc) + timedelta(days=7)) is_active = Column(Boolean, default=True, nullable=False) group = relationship("Group", back_populates="invites") creator = relationship("User", back_populates="created_invites") # === NEW: List Model === class List(Base): __tablename__ = "lists" id = Column(Integer, primary_key=True, index=True) name = Column(String, index=True, nullable=False) description = Column(Text, nullable=True) created_by_id = Column(Integer, ForeignKey("users.id"), nullable=False) # Who created this list group_id = Column(Integer, ForeignKey("groups.id"), nullable=True) # Which group it belongs to (NULL if personal) is_complete = Column(Boolean, default=False, nullable=False) created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False) updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False) # --- Relationships --- creator = relationship("User", back_populates="created_lists") # Link to User.created_lists group = relationship("Group", back_populates="lists") # Link to Group.lists items = relationship("Item", back_populates="list", cascade="all, delete-orphan", order_by="Item.created_at") # Link to Item.list, cascade deletes # === NEW: Item Model === class Item(Base): __tablename__ = "items" id = Column(Integer, primary_key=True, index=True) list_id = Column(Integer, ForeignKey("lists.id", ondelete="CASCADE"), nullable=False) # Belongs to which list name = Column(String, index=True, nullable=False) quantity = Column(String, nullable=True) # Flexible quantity (e.g., "1", "2 lbs", "a bunch") is_complete = Column(Boolean, default=False, nullable=False) price = Column(Numeric(10, 2), nullable=True) # For cost splitting later (e.g., 12345678.99) added_by_id = Column(Integer, ForeignKey("users.id"), nullable=False) # Who added this item completed_by_id = Column(Integer, ForeignKey("users.id"), nullable=True) # Who marked it complete created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False) updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False) # --- Relationships --- list = relationship("List", back_populates="items") # Link to List.items added_by_user = relationship("User", foreign_keys=[added_by_id], back_populates="added_items") # Link to User.added_items completed_by_user = relationship("User", foreign_keys=[completed_by_id], back_populates="completed_items") # Link to User.completed_items