Files
ewoooc/database/user_models.py
ogt 1b4f3a7bbe
Some checks failed
CD Pipeline / deploy (push) Failing after 59s
feat: EwoooC 初始化 — 完整專案推版至 Gitea
- 建立 Gitea Actions CD pipeline (.gitea/workflows/cd.yaml)
- 部署模式: rsync Python 檔案至 188 → docker restart (volume mount)
- Dockerfile/requirements 變動時自動重建 Docker image
- 部署通知: Telegram (開始/成功/失敗)
- 健康檢查: https://mo.wooo.work/health (最多 5 次重試)
- 同步最新 CLAUDE.md / ADR-008 / memory (2026-04-19)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 01:21:13 +08:00

111 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
用戶與登入歷史資料模型
提供:
- 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 已載入")