"""OAuth linked account model for Mantimon TCG. This module defines the OAuthLinkedAccount model for supporting multiple OAuth providers per user (account linking). A user can have multiple linked accounts (e.g., both Google and Discord), allowing them to log in with either provider. Example: # User links Discord to their existing Google account linked_account = OAuthLinkedAccount( user_id=user.id, provider="discord", oauth_id="123456789", email="player@example.com" ) """ from datetime import datetime from typing import TYPE_CHECKING from sqlalchemy import DateTime, ForeignKey, Index, String, func from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import Mapped, mapped_column, relationship from app.db.base import Base if TYPE_CHECKING: from app.db.models.user import User class OAuthLinkedAccount(Base): """Linked OAuth account for multi-provider authentication. Allows users to link multiple OAuth providers to a single account, enabling login via any linked provider. The User model still has oauth_provider/oauth_id for the "primary" provider (the one used to create the account). This table tracks additional linked providers. Attributes: id: Unique identifier (UUID). user_id: Foreign key to the user who owns this linked account. provider: OAuth provider name ('google', 'discord'). oauth_id: Unique ID from the OAuth provider. email: Email address from this OAuth provider (may differ from user's primary email). display_name: Display name from this OAuth provider. avatar_url: Avatar URL from this OAuth provider. linked_at: When this account was linked. Relationships: user: The User who owns this linked account. """ __tablename__ = "oauth_linked_accounts" # Foreign key to user user_id: Mapped[str] = mapped_column( UUID(as_uuid=False), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True, doc="User who owns this linked account", ) # OAuth provider info provider: Mapped[str] = mapped_column( String(20), nullable=False, doc="OAuth provider name (google, discord)", ) oauth_id: Mapped[str] = mapped_column( String(255), nullable=False, doc="Unique ID from OAuth provider", ) # Additional info from provider email: Mapped[str | None] = mapped_column( String(255), nullable=True, doc="Email from this OAuth provider", ) display_name: Mapped[str | None] = mapped_column( String(100), nullable=True, doc="Display name from this OAuth provider", ) avatar_url: Mapped[str | None] = mapped_column( String(500), nullable=True, doc="Avatar URL from this OAuth provider", ) # When linked linked_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), nullable=False, doc="When this account was linked", ) # Relationship back to user user: Mapped["User"] = relationship( "User", back_populates="linked_accounts", ) # Indexes and constraints __table_args__ = ( # Each OAuth provider+ID can only be linked to one user Index( "ix_oauth_linked_accounts_provider_oauth_id", "provider", "oauth_id", unique=True, ), ) def __repr__(self) -> str: return f""