68 lines
2.5 KiB
Python
68 lines
2.5 KiB
Python
"""User model. Argon2 password hashing, role/trust fields, Flask-Login mixin."""
|
|
from datetime import datetime
|
|
from flask_login import UserMixin
|
|
from app.extensions import db
|
|
from app.models.enums import Role, UserStatus, TrustTier
|
|
from app.utils.security import hash_password, verify_password
|
|
|
|
|
|
class User(UserMixin, db.Model):
|
|
__tablename__ = "users"
|
|
|
|
id = db.Column(db.BigInteger().with_variant(db.Integer, "sqlite"), primary_key=True, autoincrement=True)
|
|
email = db.Column(db.String(255), unique=True, nullable=False, index=True)
|
|
password_hash = db.Column(db.String(255), nullable=False)
|
|
display_name = db.Column(db.String(80), nullable=False)
|
|
|
|
role = db.Column(db.Enum(Role), nullable=False, default=Role.free)
|
|
status = db.Column(db.Enum(UserStatus), nullable=False, default=UserStatus.active)
|
|
locale = db.Column(db.String(5), nullable=False, default="en")
|
|
|
|
tier_id = db.Column(db.BigInteger, db.ForeignKey("plans.id"), nullable=True)
|
|
tier = db.relationship("Plan", back_populates="users")
|
|
|
|
trust_score = db.Column(db.Integer, nullable=False, default=0)
|
|
trust_tier = db.Column(db.Enum(TrustTier), nullable=False, default=TrustTier.new)
|
|
|
|
verified = db.Column(db.Boolean, nullable=False, default=False) # identity/badge
|
|
email_verified = db.Column(db.Boolean, nullable=False, default=False)
|
|
|
|
last_login_at = db.Column(db.DateTime, nullable=True)
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
|
|
updated_at = db.Column(db.DateTime, default=datetime.utcnow,
|
|
onupdate=datetime.utcnow, nullable=False)
|
|
|
|
trust_events = db.relationship("TrustEvent", back_populates="user",
|
|
lazy="dynamic", cascade="all, delete-orphan")
|
|
|
|
# --- password ---
|
|
def set_password(self, raw):
|
|
self.password_hash = hash_password(raw)
|
|
|
|
def check_password(self, raw):
|
|
return verify_password(self.password_hash, raw)
|
|
|
|
# --- RBAC helpers ---
|
|
@property
|
|
def is_admin(self):
|
|
return self.role == Role.admin
|
|
|
|
@property
|
|
def is_moderator(self):
|
|
return self.role in (Role.moderator, Role.admin)
|
|
|
|
@property
|
|
def is_subscriber(self):
|
|
return self.role == Role.subscriber
|
|
|
|
def has_role(self, *roles):
|
|
return self.role in roles
|
|
|
|
@property
|
|
def is_active(self):
|
|
# Flask-Login uses this to permit login.
|
|
return self.status == UserStatus.active
|
|
|
|
def __repr__(self):
|
|
return f"<User {self.email}>"
|