# app/models.py import enum from datetime import datetime from sqlalchemy import ( Column, Integer, String, DateTime, ForeignKey, Boolean, Enum as SAEnum, # Renamed to avoid clash with Python's enum UniqueConstraint, event, DDL ) from sqlalchemy.orm import relationship from sqlalchemy.sql import func # For server_default=func.now() from app.database import Base # Import Base from database setup # Define Enum for User Roles in Groups 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) # Allow nullable name initially created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False) # Relationships # Groups created by this user created_groups = relationship("Group", back_populates="creator") # Association object for group membership group_associations = relationship("UserGroup", back_populates="user", cascade="all, delete-orphan") # Items added by this user (Add later when Item model is defined) # added_items = relationship("Item", foreign_keys="[Item.added_by_id]", back_populates="added_by_user") # Items completed by this user (Add later) # completed_items = relationship("Item", foreign_keys="[Item.completed_by_id]", back_populates="completed_by_user") # Expense shares for this user (Add later) # expense_shares = relationship("ExpenseShare", back_populates="user") # Lists created by this user (Add later) # created_lists = relationship("List", foreign_keys="[List.created_by_id]", back_populates="creator") # --- 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 # The user who created this group creator = relationship("User", back_populates="created_groups") # Association object for group membership member_associations = relationship("UserGroup", back_populates="group", cascade="all, delete-orphan") # Lists belonging to this group (Add later) # lists = relationship("List", back_populates="group") # --- UserGroup Association Model --- class UserGroup(Base): __tablename__ = "user_groups" __table_args__ = (UniqueConstraint('user_id', 'group_id', name='uq_user_group'),) # Ensure user cannot be in same group twice id = Column(Integer, primary_key=True, index=True) # Surrogate primary key 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), nullable=False, default=UserRoleEnum.member) joined_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False) # Relationships back to User and Group user = relationship("User", back_populates="group_associations") group = relationship("Group", back_populates="member_associations") # --- Add other models below when needed --- # class List(Base): ... # class Item(Base): ... # class Expense(Base): ... # class ExpenseShare(Base): ... # Optional: Trigger for automatically creating an 'owner' UserGroup entry when a Group is created. # This requires importing event and DDL. It's advanced and DB-specific, might be simpler to handle in application logic. # Example for PostgreSQL (might need adjustment): # group_owner_trigger = DDL(""" # CREATE OR REPLACE FUNCTION add_group_owner() # RETURNS TRIGGER AS $$ # BEGIN # INSERT INTO user_groups (user_id, group_id, role, joined_at) # VALUES (NEW.created_by_id, NEW.id, 'owner', NOW()); # RETURN NEW; # END; # $$ LANGUAGE plpgsql; # # CREATE TRIGGER trg_add_group_owner # AFTER INSERT ON groups # FOR EACH ROW EXECUTE FUNCTION add_group_owner(); # """) # event.listen(Group.__table__, 'after_create', group_owner_trigger)