""" 用戶與登入歷史資料模型 提供: - User: 用戶帳號表 - LoginHistory: 登入歷史記錄表 """ from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey, Text from sqlalchemy.orm import relationship from datetime import datetime from database.models import Base class User(Base): """用戶帳號表""" __tablename__ = 'users' id = Column(Integer, primary_key=True) username = Column(String(50), unique=True, nullable=False, index=True) email = Column(String(120), unique=True, nullable=True) password_hash = Column(String(256), nullable=False) role = Column(String(20), default='user', index=True) # admin, manager, user display_name = Column(String(100)) is_active = Column(Boolean, default=True) password_changed_at = Column(DateTime) # 密碼變更時間 created_at = Column(DateTime, default=datetime.now) updated_at = Column(DateTime, onupdate=datetime.now) created_by = Column(Integer, ForeignKey('users.id'), nullable=True) # 關聯 login_history = relationship("LoginHistory", back_populates="user", cascade="all, delete-orphan") # 角色常數 ROLE_ADMIN = 'admin' ROLE_MANAGER = 'manager' ROLE_USER = 'user' ROLES = [ROLE_ADMIN, ROLE_MANAGER, ROLE_USER] ROLE_LABELS = { 'admin': '系統管理員', 'manager': '管理者', 'user': '一般用戶' } def get_role_label(self): """取得角色顯示名稱""" return self.ROLE_LABELS.get(self.role, self.role) def is_admin(self): """是否為管理員""" return self.role == self.ROLE_ADMIN def is_manager_or_above(self): """是否為管理者或以上""" return self.role in [self.ROLE_ADMIN, self.ROLE_MANAGER] def to_dict(self): """轉換為字典""" return { 'id': self.id, 'username': self.username, 'email': self.email, 'role': self.role, 'role_label': self.get_role_label(), 'display_name': self.display_name, 'is_active': self.is_active, 'password_changed_at': self.password_changed_at.isoformat() if self.password_changed_at else None, 'created_at': self.created_at.isoformat() if self.created_at else None, 'updated_at': self.updated_at.isoformat() if self.updated_at else None, } class LoginHistory(Base): """登入歷史記錄表""" __tablename__ = 'login_history' id = Column(Integer, primary_key=True) user_id = Column(Integer, ForeignKey('users.id'), nullable=True) # 允許 NULL,記錄失敗的登入嘗試 username_attempted = Column(String(50)) # 嘗試登入的帳號名稱 login_time = Column(DateTime, default=datetime.now, index=True) ip_address = Column(String(45)) # 支援 IPv6 user_agent = Column(String(256)) status = Column(String(20), index=True) # success, failed, locked failure_reason = Column(String(100)) # 關聯 user = relationship("User", back_populates="login_history") # 狀態常數 STATUS_SUCCESS = 'success' STATUS_FAILED = 'failed' STATUS_LOCKED = 'locked' def to_dict(self): """轉換為字典""" return { 'id': self.id, 'user_id': self.user_id, 'username_attempted': self.username_attempted, 'login_time': self.login_time.isoformat() if self.login_time else None, 'ip_address': self.ip_address, 'user_agent': self.user_agent, 'status': self.status, 'failure_reason': self.failure_reason, } print("✅ User models 已載入")