69 lines
2.2 KiB
Python
69 lines
2.2 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import datetime, timezone
|
|
from enum import Enum
|
|
|
|
from flask_login import UserMixin
|
|
from sqlalchemy import Boolean, DateTime, String
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
from werkzeug.security import check_password_hash, generate_password_hash
|
|
|
|
from app.extensions import db
|
|
|
|
|
|
def utcnow() -> datetime:
|
|
return datetime.now(timezone.utc)
|
|
|
|
|
|
class Role(str, Enum):
|
|
ADMIN = "admin"
|
|
OPERATOR = "operator"
|
|
VIEWER = "viewer"
|
|
|
|
@property
|
|
def label(self) -> str:
|
|
return self.value.title()
|
|
|
|
|
|
class Account(UserMixin, db.Model):
|
|
__tablename__ = "accounts"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
username: Mapped[str] = mapped_column(String(80), unique=True, index=True, nullable=False)
|
|
email: Mapped[str] = mapped_column(String(255), unique=True, index=True, nullable=False)
|
|
full_name: Mapped[str] = mapped_column(String(160), nullable=False)
|
|
role: Mapped[str] = mapped_column(String(20), default=Role.VIEWER.value, nullable=False)
|
|
password_hash: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, nullable=False)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True),
|
|
default=utcnow,
|
|
onupdate=utcnow,
|
|
nullable=False,
|
|
)
|
|
|
|
attendance_events = relationship("AttendanceEvent", back_populates="created_by")
|
|
|
|
@property
|
|
def is_active(self) -> bool:
|
|
return self.active
|
|
|
|
def set_password(self, password: str) -> None:
|
|
self.password_hash = generate_password_hash(password, method="scrypt")
|
|
|
|
def check_password(self, password: str) -> bool:
|
|
return check_password_hash(self.password_hash, password)
|
|
|
|
def has_role(self, *roles: str | Role) -> bool:
|
|
normalized = {role.value if isinstance(role, Role) else role for role in roles}
|
|
return self.role in normalized
|
|
|
|
@property
|
|
def is_admin(self) -> bool:
|
|
return self.role == Role.ADMIN.value
|
|
|
|
@property
|
|
def can_operate(self) -> bool:
|
|
return self.role in {Role.ADMIN.value, Role.OPERATOR.value}
|