nex_math/backend/routers/users.py

145 lines
4.6 KiB
Python

"""管理员用户管理。"""
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from database import get_db
from dependencies import require_permission
from models import Role, User, UserRole
from routers.auth import user_to_out
from schemas import (
ResetPasswordIn,
RoleOut,
UserCreate,
UserOut,
UserUpdate,
)
from seed.rbac import ROLES
from services.learning_profile import (
ensure_profile,
remove_unused_admin_profile,
reset_all_learning,
)
from services.security import hash_password
router = APIRouter(prefix="/users", tags=["users"], dependencies=[])
@router.post("/reset-learning")
def reset_learning(
db: Session = Depends(get_db),
_: User = Depends(require_permission("users:manage")),
):
"""清空所有用户的学习过程数据(保留用户/教材/题库/知识点图谱)。"""
reset_all_learning(db)
return {"ok": True, "message": "所有学习过程数据已清空,可从新的章节任务重新开始"}
def _role(db: Session, code: str) -> Role:
role = db.query(Role).filter(Role.code == code).first()
if role is None:
raise HTTPException(status_code=400, detail="角色不存在")
return role
@router.get("", response_model=list[UserOut])
def list_users(
role_code: str = "",
db: Session = Depends(get_db),
_: User = Depends(require_permission("users:manage")),
):
query = db.query(User)
if role_code:
query = (
query.join(UserRole, UserRole.user_id == User.id)
.join(Role, Role.id == UserRole.role_id)
.filter(Role.code == role_code)
)
users = query.order_by(User.id.asc()).all()
return [user_to_out(db, user) for user in users]
@router.get("/roles", response_model=list[RoleOut])
def list_roles(
db: Session = Depends(get_db),
_: User = Depends(require_permission("users:manage")),
):
return [RoleOut.model_validate(role) for role in db.query(Role).order_by(Role.id).all()]
@router.post("", response_model=UserOut)
def create_user(
payload: UserCreate,
db: Session = Depends(get_db),
_: User = Depends(require_permission("users:manage")),
):
if payload.role_code not in ROLES:
raise HTTPException(status_code=400, detail="不支持的角色")
if db.query(User).filter(User.username == payload.username).first():
raise HTTPException(status_code=400, detail="用户名已存在")
role = _role(db, payload.role_code)
user = User(
username=payload.username,
display_name=payload.display_name or payload.username,
password_hash=hash_password(payload.password),
active=True,
)
db.add(user)
db.flush()
db.add(UserRole(user_id=user.id, role_id=role.id))
db.commit()
if role.code != "admin":
ensure_profile(db, user.id)
else:
remove_unused_admin_profile(db, user.id)
db.refresh(user)
return user_to_out(db, user)
@router.put("/{user_id}", response_model=UserOut)
def update_user(
user_id: int,
payload: UserUpdate,
db: Session = Depends(get_db),
operator: User = Depends(require_permission("users:manage")),
):
user = db.get(User, user_id)
if user is None:
raise HTTPException(status_code=404, detail="用户不存在")
if user.id == operator.id and payload.active is False:
raise HTTPException(status_code=400, detail="不能停用自己的账号")
if payload.display_name is not None:
user.display_name = payload.display_name
if payload.active is not None:
user.active = payload.active
if payload.role_code is not None:
role = _role(db, payload.role_code)
if user.id == operator.id and role.code != "admin":
raise HTTPException(status_code=400, detail="不能移除自己的管理员角色")
db.query(UserRole).filter(UserRole.user_id == user.id).delete()
db.add(UserRole(user_id=user.id, role_id=role.id))
if role.code == "admin":
remove_unused_admin_profile(db, user.id)
else:
ensure_profile(db, user.id)
db.commit()
return user_to_out(db, user)
@router.post("/{user_id}/reset-password")
def reset_password(
user_id: int,
payload: ResetPasswordIn,
db: Session = Depends(get_db),
_: User = Depends(require_permission("users:manage")),
):
user = db.get(User, user_id)
if user is None:
raise HTTPException(status_code=404, detail="用户不存在")
user.password_hash = hash_password(payload.password)
db.commit()
return {"ok": True, "message": f"已重置 {user.username} 的密码。"}