from datetime import datetime from enum import Enum from typing import Optional from sqlalchemy import Boolean, CheckConstraint, DateTime, Float, ForeignKey, Integer, String, Text, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column, relationship from .database import Base class UserStatus(str, Enum): pending = "pending" active = "active" suspended = "suspended" class User(Base): __tablename__ = "users" id: Mapped[int] = mapped_column(Integer, primary_key=True) email_or_phone: Mapped[Optional[str]] = mapped_column(String(320), unique=True, index=True) password_hash: Mapped[Optional[str]] = mapped_column(String(256)) status: Mapped[UserStatus] = mapped_column(String(20), default=UserStatus.active) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) profile: Mapped[Optional["UserProfile"]] = relationship(back_populates="user", cascade="all, delete-orphan", uselist=False) location: Mapped[Optional["UserLocation"]] = relationship(back_populates="user", cascade="all, delete-orphan", uselist=False) identities: Mapped[list["AuthIdentity"]] = relationship(back_populates="user", cascade="all, delete-orphan") media: Mapped[list["UserMedia"]] = relationship(back_populates="user", cascade="all, delete-orphan") posts: Mapped[list["Post"]] = relationship(back_populates="user", cascade="all, delete-orphan") post_likes: Mapped[list["PostLike"]] = relationship(back_populates="user", cascade="all, delete-orphan") post_comments: Mapped[list["PostComment"]] = relationship(back_populates="user", cascade="all, delete-orphan") sent_messages: Mapped[list["ChatMessage"]] = relationship(back_populates="sender", cascade="all, delete-orphan") sessions: Mapped[list["UserSession"]] = relationship(back_populates="user", cascade="all, delete-orphan") question_answers: Mapped[list["UserQuestionAnswer"]] = relationship(back_populates="user", cascade="all, delete-orphan") interests: Mapped[list["UserInterest"]] = relationship(back_populates="user", cascade="all, delete-orphan", order_by="UserInterest.id") class AuthIdentity(Base): __tablename__ = "auth_identities" __table_args__ = (UniqueConstraint("provider", "provider_user_id"),) id: Mapped[int] = mapped_column(primary_key=True) user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True) provider: Mapped[str] = mapped_column(String(30)) provider_user_id: Mapped[str] = mapped_column(String(255)) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) user: Mapped[User] = relationship(back_populates="identities") class UserProfile(Base): __tablename__ = "user_profiles" id: Mapped[int] = mapped_column(primary_key=True) user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), unique=True) full_name: Mapped[Optional[str]] = mapped_column(String(120)) gender: Mapped[Optional[str]] = mapped_column(String(30)) birth_date: Mapped[Optional[str]] = mapped_column(String(10)) city: Mapped[Optional[str]] = mapped_column(String(120)) county: Mapped[Optional[str]] = mapped_column(String(120)) country: Mapped[Optional[str]] = mapped_column(String(120)) relationship_goal: Mapped[Optional[str]] = mapped_column(String(30)) bio: Mapped[Optional[str]] = mapped_column(Text) occupation: Mapped[Optional[str]] = mapped_column(String(120)) education: Mapped[Optional[str]] = mapped_column(String(120)) height_cm: Mapped[Optional[int]] = mapped_column(Integer) beliefs: Mapped[Optional[str]] = mapped_column(String(120)) smoking: Mapped[Optional[str]] = mapped_column(String(50)) drinking: Mapped[Optional[str]] = mapped_column(String(50)) pets: Mapped[Optional[str]] = mapped_column(String(120)) exercise: Mapped[Optional[str]] = mapped_column(String(50)) user: Mapped[User] = relationship(back_populates="profile") class UserInterest(Base): __tablename__ = "user_interests" __table_args__ = (UniqueConstraint("user_id", "name", name="uq_user_interest_name"),) id: Mapped[int] = mapped_column(primary_key=True) user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True) name: Mapped[str] = mapped_column(String(120)) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) user: Mapped[User] = relationship(back_populates="interests") class UserLocation(Base): __tablename__ = "user_locations" id: Mapped[int] = mapped_column(primary_key=True) user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), unique=True, index=True) latitude: Mapped[float] = mapped_column(Float) longitude: Mapped[float] = mapped_column(Float) updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) user: Mapped[User] = relationship(back_populates="location") class UserSession(Base): __tablename__ = "user_sessions" id: Mapped[int] = mapped_column(primary_key=True) user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True) refresh_token_hash: Mapped[str] = mapped_column(String(256), unique=True) expires_at: Mapped[datetime] = mapped_column(DateTime, index=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) revoked_at: Mapped[Optional[datetime]] = mapped_column(DateTime) user: Mapped[User] = relationship(back_populates="sessions") class UserMedia(Base): __tablename__ = "user_media" id: Mapped[int] = mapped_column(primary_key=True) user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True) media_type: Mapped[str] = mapped_column(String(20)) storage_path: Mapped[str] = mapped_column(String(500)) original_name: Mapped[Optional[str]] = mapped_column(String(255)) mime_type: Mapped[Optional[str]] = mapped_column(String(100)) review_status: Mapped[str] = mapped_column(String(20), default="pending") created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) user: Mapped[User] = relationship(back_populates="media") class Swipe(Base): __tablename__ = "swipes" __table_args__ = ( UniqueConstraint("actor_id", "target_id", name="uq_swipe_actor_target"), CheckConstraint("actor_id <> target_id", name="ck_swipe_not_self"), CheckConstraint("direction IN ('like', 'pass', 'super_like')", name="ck_swipe_direction"), ) id: Mapped[int] = mapped_column(primary_key=True) actor_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True) target_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True) direction: Mapped[str] = mapped_column(String(10)) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) class Match(Base): __tablename__ = "matches" __table_args__ = ( UniqueConstraint("user1_id", "user2_id", name="uq_match_user_pair"), CheckConstraint("user1_id < user2_id", name="ck_match_canonical_order"), ) id: Mapped[int] = mapped_column(primary_key=True) user1_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True) user2_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True) matched_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) conversation: Mapped[Optional["Conversation"]] = relationship(back_populates="match", uselist=False, cascade="all, delete-orphan", single_parent=True) class Conversation(Base): __tablename__ = "conversations" id: Mapped[int] = mapped_column(primary_key=True) match_id: Mapped[int] = mapped_column(ForeignKey("matches.id", ondelete="CASCADE"), unique=True, index=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) match: Mapped[Match] = relationship(back_populates="conversation") messages: Mapped[list["ChatMessage"]] = relationship(back_populates="conversation", cascade="all, delete-orphan") class ChatMessage(Base): __tablename__ = "chat_messages" id: Mapped[int] = mapped_column(primary_key=True) conversation_id: Mapped[int] = mapped_column(ForeignKey("conversations.id", ondelete="CASCADE"), index=True) sender_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True) content: Mapped[str] = mapped_column(Text) message_type: Mapped[str] = mapped_column(String(20), default="text") created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, index=True) deleted_at: Mapped[Optional[datetime]] = mapped_column(DateTime) conversation: Mapped[Conversation] = relationship(back_populates="messages") sender: Mapped[User] = relationship(back_populates="sent_messages") class Post(Base): __tablename__ = "posts" id: Mapped[int] = mapped_column(primary_key=True) user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True) content: Mapped[str] = mapped_column(Text) post_type: Mapped[str] = mapped_column(String(20), default="text") content_format: Mapped[str] = mapped_column(String(10), default="plain", server_default="plain") feeling: Mapped[Optional[str]] = mapped_column(String(50)) location_name: Mapped[Optional[str]] = mapped_column(String(255)) location_latitude: Mapped[Optional[float]] = mapped_column(Float) location_longitude: Mapped[Optional[float]] = mapped_column(Float) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, index=True) updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) user: Mapped[User] = relationship(back_populates="posts") likes: Mapped[list["PostLike"]] = relationship(back_populates="post", cascade="all, delete-orphan") comments: Mapped[list["PostComment"]] = relationship(back_populates="post", cascade="all, delete-orphan") media: Mapped[list["PostMedia"]] = relationship( back_populates="post", cascade="all, delete-orphan", order_by="PostMedia.position" ) class PostMedia(Base): __tablename__ = "post_media" id: Mapped[int] = mapped_column(primary_key=True) post_id: Mapped[int] = mapped_column(ForeignKey("posts.id", ondelete="CASCADE"), index=True) media_type: Mapped[str] = mapped_column(String(20)) storage_path: Mapped[str] = mapped_column(String(500)) original_name: Mapped[Optional[str]] = mapped_column(String(255)) mime_type: Mapped[str] = mapped_column(String(100)) size_bytes: Mapped[int] = mapped_column(Integer) position: Mapped[int] = mapped_column(Integer, default=0) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) post: Mapped[Post] = relationship(back_populates="media") class PostLike(Base): __tablename__ = "post_likes" __table_args__ = (UniqueConstraint("post_id", "user_id", name="uq_post_like"),) id: Mapped[int] = mapped_column(primary_key=True) post_id: Mapped[int] = mapped_column(ForeignKey("posts.id", ondelete="CASCADE"), index=True) user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) post: Mapped[Post] = relationship(back_populates="likes") user: Mapped[User] = relationship(back_populates="post_likes") class PostComment(Base): __tablename__ = "post_comments" id: Mapped[int] = mapped_column(primary_key=True) post_id: Mapped[int] = mapped_column(ForeignKey("posts.id", ondelete="CASCADE"), index=True) user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True) parent_comment_id: Mapped[Optional[int]] = mapped_column( ForeignKey("post_comments.id", ondelete="CASCADE"), index=True ) content: Mapped[str] = mapped_column(Text) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, index=True) updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) post: Mapped[Post] = relationship(back_populates="comments") user: Mapped[User] = relationship(back_populates="post_comments") parent: Mapped[Optional["PostComment"]] = relationship(remote_side=[id], back_populates="replies") replies: Mapped[list["PostComment"]] = relationship(back_populates="parent", cascade="all, delete-orphan") likes: Mapped[list["CommentLike"]] = relationship(back_populates="comment", cascade="all, delete-orphan") class CommentLike(Base): __tablename__ = "comment_likes" __table_args__ = (UniqueConstraint("comment_id", "user_id", name="uq_comment_like"),) id: Mapped[int] = mapped_column(primary_key=True) comment_id: Mapped[int] = mapped_column(ForeignKey("post_comments.id", ondelete="CASCADE"), index=True) user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) comment: Mapped["PostComment"] = relationship(back_populates="likes") user: Mapped[User] = relationship() class FeedQuestion(Base): __tablename__ = "feed_questions" id: Mapped[int] = mapped_column(primary_key=True) code: Mapped[str] = mapped_column(String(100), unique=True) question_text: Mapped[str] = mapped_column(String(500)) question_text_zh: Mapped[Optional[str]] = mapped_column(String(500)) answer_type: Mapped[str] = mapped_column(String(20), default="boolean") is_active: Mapped[bool] = mapped_column(Boolean, default=True, index=True) sort_order: Mapped[int] = mapped_column(Integer, default=0) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) answers: Mapped[list["UserQuestionAnswer"]] = relationship(back_populates="question", cascade="all, delete-orphan") class UserQuestionAnswer(Base): __tablename__ = "user_question_answers" __table_args__ = (UniqueConstraint("user_id", "question_id", name="uq_user_question_answer"),) id: Mapped[int] = mapped_column(primary_key=True) user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True) question_id: Mapped[int] = mapped_column(ForeignKey("feed_questions.id", ondelete="CASCADE"), index=True) boolean_answer: Mapped[Optional[bool]] = mapped_column(Boolean) text_answer: Mapped[Optional[str]] = mapped_column(Text) answered_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) user: Mapped[User] = relationship(back_populates="question_answers") question: Mapped[FeedQuestion] = relationship(back_populates="answers")