Bruno Charest c3cd7c2621
Some checks failed
Tests / Backend Tests (Python) (3.10) (push) Has been cancelled
Tests / Backend Tests (Python) (3.11) (push) Has been cancelled
Tests / Backend Tests (Python) (3.12) (push) Has been cancelled
Tests / Frontend Tests (JS) (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / All Tests Passed (push) Has been cancelled
feat: Implement initial Homelab Automation API v2 with new models, routes, and core architecture, including a SQLAlchemy model refactoring script.
2026-03-03 20:18:22 -05:00

89 lines
3.0 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 List, Optional
from sqlalchemy import Boolean, DateTime, String, Text, text
from sqlalchemy.orm import Mapped, mapped_column, relationship
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[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[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[datetime] = mapped_column(DateTime(timezone=True), nullable=True)
password_changed_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=True)
# Soft delete support
deleted_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=True)
# Relationships
terminal_commands: Mapped[List["TerminalCommandLog"]] = relationship(
"TerminalCommandLog", back_populates="user"
)
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