"""Data models for authenticated users and issued API sessions.""" import uuid from datetime import UTC, datetime from enum import Enum from sqlalchemy import Boolean, DateTime, Enum as SqlEnum, ForeignKey, String from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import Mapped, mapped_column, relationship from app.db.base import Base class UserRole(str, Enum): """Declares authorization roles used for API route access control.""" ADMIN = "admin" USER = "user" class AppUser(Base): """Stores one authenticatable user account with role-bound authorization.""" __tablename__ = "app_users" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) username: Mapped[str] = mapped_column(String(128), nullable=False, unique=True, index=True) password_hash: Mapped[str] = mapped_column(String(512), nullable=False) role: Mapped[UserRole] = mapped_column(SqlEnum(UserRole), nullable=False, default=UserRole.USER) is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, default=lambda: datetime.now(UTC)) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC), ) sessions: Mapped[list["AuthSession"]] = relationship( "AuthSession", back_populates="user", cascade="all, delete-orphan", ) class AuthSession(Base): """Stores one issued bearer session token for a specific authenticated user.""" __tablename__ = "auth_sessions" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) user_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("app_users.id", ondelete="CASCADE"), nullable=False, index=True) token_hash: Mapped[str] = mapped_column(String(128), nullable=False, unique=True, index=True) expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, index=True) revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) user_agent: Mapped[str | None] = mapped_column(String(512), nullable=True) ip_address: Mapped[str | None] = mapped_column(String(64), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, default=lambda: datetime.now(UTC)) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC), ) user: Mapped[AppUser] = relationship("AppUser", back_populates="sessions")