from datetime import datetime, timezone from sqlalchemy import Boolean, DateTime, ForeignKey, String, Text, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column from app.db import Base def utcnow() -> datetime: return datetime.now(timezone.utc) class AdminUser(Base): __tablename__ = "admin_user" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) username: Mapped[str] = mapped_column(String(64), unique=True, nullable=False) password_hash: Mapped[str] = mapped_column(String(255), nullable=False) display_name: Mapped[str] = mapped_column(String(64), nullable=False) mobile_cipher: Mapped[str | None] = mapped_column(Text, nullable=True) mobile_mask: Mapped[str] = mapped_column(String(32), nullable=False, default="") status: Mapped[str] = mapped_column(String(16), nullable=False, default="active") failed_login_count: Mapped[int] = mapped_column(nullable=False, default=0) locked_until: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, default=utcnow) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=utcnow, onupdate=utcnow ) class AdminRole(Base): __tablename__ = "admin_role" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) code: Mapped[str] = mapped_column(String(64), unique=True, nullable=False) name: Mapped[str] = mapped_column(String(64), nullable=False) description: Mapped[str | None] = mapped_column(Text, nullable=True) status: Mapped[str] = mapped_column(String(16), nullable=False, default="active") created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, default=utcnow) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=utcnow, onupdate=utcnow ) class AdminResource(Base): __tablename__ = "admin_resource" __table_args__ = ( # parent_id 为空时,MySQL 唯一索引不会拦住重复的根名称,根节点在服务层再查一次。 UniqueConstraint("parent_id", "name", name="uq_admin_resource_parent_name"), ) id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) parent_id: Mapped[int | None] = mapped_column(ForeignKey("admin_resource.id"), nullable=True) type: Mapped[str] = mapped_column(String(16), nullable=False) name: Mapped[str] = mapped_column(String(64), nullable=False) title: Mapped[str] = mapped_column(String(64), nullable=False) path: Mapped[str] = mapped_column(String(255), nullable=False, default="") component: Mapped[str] = mapped_column(String(255), nullable=False, default="") icon: Mapped[str] = mapped_column(String(64), nullable=False, default="") permission: Mapped[str] = mapped_column(String(64), nullable=False, default="") sort: Mapped[int] = mapped_column(nullable=False, default=0) visible: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) status: Mapped[str] = mapped_column(String(16), nullable=False, default="active") class AdminUserRole(Base): __tablename__ = "admin_user_role" user_id: Mapped[int] = mapped_column(ForeignKey("admin_user.id", ondelete="CASCADE"), primary_key=True) role_id: Mapped[int] = mapped_column(ForeignKey("admin_role.id", ondelete="CASCADE"), primary_key=True) class AdminRoleResource(Base): __tablename__ = "admin_role_resource" role_id: Mapped[int] = mapped_column(ForeignKey("admin_role.id", ondelete="CASCADE"), primary_key=True) resource_id: Mapped[int] = mapped_column( ForeignKey("admin_resource.id", ondelete="CASCADE"), primary_key=True )