Coverage for src / idx_api / models / user.py: 100%
20 statements
« prev ^ index » next coverage.py v7.13.1, created at 2025-12-28 11:09 -0700
« prev ^ index » next coverage.py v7.13.1, created at 2025-12-28 11:09 -0700
1"""User model for authentication."""
3from datetime import datetime
4from typing import TYPE_CHECKING, Optional
6from sqlalchemy import DateTime, ForeignKey, String
7from sqlalchemy.orm import Mapped, mapped_column, relationship
9from idx_api.models.base import Base, TimestampMixin
11if TYPE_CHECKING:
12 from idx_api.models.agent import Agent
13 from idx_api.models.api_key import APIKey
14 from idx_api.models.broker import Broker
15 from idx_api.models.brokerage import Brokerage
18class User(Base, TimestampMixin):
19 """Application user with role-based access control."""
21 __tablename__ = "users"
23 id: Mapped[int] = mapped_column(primary_key=True)
25 # Authentication
26 keycloak_user_id: Mapped[Optional[str]] = mapped_column(
27 String(100), unique=True, index=True
28 ) # UUID from Keycloak
29 email: Mapped[str] = mapped_column(String(255), unique=True, nullable=False, index=True)
31 # Authorization - role hierarchy: admin (4) > broker (2) > agent (1) > viewer (0)
32 role: Mapped[str] = mapped_column(
33 String(20), nullable=False, default="viewer"
34 ) # admin, broker, agent, viewer
36 # Association with brokerage/broker contact/agent
37 brokerage_id: Mapped[Optional[int]] = mapped_column(ForeignKey("brokerages.id"), index=True)
38 broker_id: Mapped[Optional[int]] = mapped_column(ForeignKey("brokers.id"), index=True)
39 agent_id: Mapped[Optional[int]] = mapped_column(ForeignKey("agents.id"), index=True)
41 # Activity tracking
42 last_login_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
44 # Soft delete
45 disabled_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
47 # Relationships
48 brokerage: Mapped[Optional["Brokerage"]] = relationship(back_populates="users")
49 broker_contact: Mapped[Optional["Broker"]] = relationship(back_populates="users")
50 agent: Mapped[Optional["Agent"]] = relationship(back_populates="users")
51 api_keys: Mapped[list["APIKey"]] = relationship(back_populates="user")
53 def __repr__(self) -> str:
54 return f"<User(id={self.id}, email='{self.email}', role='{self.role}')>"