"""管理员用户管理。""" 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} 的密码。"}