84 lines
2.9 KiB
Python

"""User model for authentication and authorization.
Designed for single-user now but prepared for multi-user with roles in the future.
"""
from __future__ import annotations
from datetime import datetime
from enum import Enum
from typing import Optional
from sqlalchemy import Boolean, DateTime, String, Text, text
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.sql import func
from .database import Base
class UserRole(str, Enum):
"""User roles for authorization.
Current implementation: single admin user.
Future: can be extended with more granular roles.
"""
ADMIN = "admin"
OPERATOR = "operator" # Future: can execute tasks but not manage users
VIEWER = "viewer" # Future: read-only access
class User(Base):
"""User model for authentication.
Fields prepared for future multi-user support:
- role: determines access level
- is_active: allows disabling users without deletion
- last_login: track user activity
- password_changed_at: for password rotation policies
"""
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
username: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, index=True)
email: Mapped[Optional[str]] = mapped_column(String(255), unique=True, nullable=True)
hashed_password: Mapped[str] = mapped_column(String(255), nullable=False)
# Role-based access control (prepared for future)
role: Mapped[str] = mapped_column(
String(20),
nullable=False,
server_default=text("'admin'") # Default to admin for single-user setup
)
# Account status
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default=text("1"))
is_superuser: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default=text("0"))
# Display name (optional)
display_name: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
# Timestamps
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
nullable=False,
server_default=func.now()
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
nullable=False,
server_default=func.now(),
onupdate=func.now()
)
last_login: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
password_changed_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
# Soft delete support
deleted_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
def __repr__(self) -> str:
return f"<User id={self.id} username={self.username} role={self.role}>"
@property
def is_admin(self) -> bool:
"""Check if user has admin privileges."""
return self.role == UserRole.ADMIN.value or self.is_superuser