""" Admin 用户管理 API(仅平台管理员可访问) """ from __future__ import annotations import logging import uuid from datetime import datetime from typing import List, Optional from fastapi import APIRouter, Depends, HTTPException, Query from pydantic import BaseModel, Field, EmailStr from sqlalchemy.orm import Session from sqlalchemy import func from app.api.auth import get_current_user from app.core.database import get_db from app.core.security import get_password_hash from app.models.user import User from app.models.workspace import WorkspaceMembership logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/v1/admin/users", tags=["admin-users"]) def _require_admin(current_user: User = Depends(get_current_user)) -> User: if current_user.role != "admin": raise HTTPException(status_code=403, detail="仅平台管理员可访问") return current_user # ── Request / Response schemas ── class UserCreateRequest(BaseModel): username: str = Field(..., min_length=2, max_length=50) email: EmailStr password: str = Field(..., min_length=6, max_length=128) role: str = Field(default="user") phone: Optional[str] = None status: str = Field(default="active") class UserUpdateRequest(BaseModel): username: Optional[str] = Field(None, min_length=2, max_length=50) email: Optional[EmailStr] = None role: Optional[str] = None phone: Optional[str] = None status: Optional[str] = None subscription_tier: Optional[str] = None class ResetPasswordRequest(BaseModel): new_password: str = Field(..., min_length=6, max_length=128) class UserItem(BaseModel): id: str username: str email: str role: str phone: Optional[str] = None phone_verified: bool = False status: str is_email_verified: bool = False subscription_tier: str = "free" daily_usage_count: int = 0 workspace_count: int = 0 created_at: Optional[datetime] = None updated_at: Optional[datetime] = None class Config: from_attributes = True class UserListResponse(BaseModel): items: List[UserItem] total: int page: int page_size: int # ── Endpoints ── @router.get("", response_model=UserListResponse) def list_users( page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100), search: Optional[str] = Query(None, description="搜索用户名或邮箱"), status: Optional[str] = Query(None, description="过滤状态: active/disabled/deleted"), role: Optional[str] = Query(None, description="过滤角色"), db: Session = Depends(get_db), _admin: User = Depends(_require_admin), ): """获取用户列表(分页、搜索、过滤)""" q = db.query(User) if search: like = f"%{search}%" q = q.filter((User.username.ilike(like)) | (User.email.ilike(like))) if status: q = q.filter(User.status == status) if role: q = q.filter(User.role == role) total = q.count() items = q.order_by(User.created_at.desc()).offset((page - 1) * page_size).limit(page_size).all() # 获取每个用户的 workspace 数量 user_ids = [u.id for u in items] ws_counts = {} if user_ids: rows = ( db.query( WorkspaceMembership.user_id, func.count(WorkspaceMembership.workspace_id).label("cnt"), ) .filter(WorkspaceMembership.user_id.in_(user_ids)) .group_by(WorkspaceMembership.user_id) .all() ) ws_counts = {r.user_id: r.cnt for r in rows} return { "items": [ {**{c.name: getattr(u, c.name) for c in User.__table__.columns}, "workspace_count": ws_counts.get(u.id, 0)} for u in items ], "total": total, "page": page, "page_size": page_size, } @router.post("", response_model=UserItem) def create_user( data: UserCreateRequest, db: Session = Depends(get_db), _admin: User = Depends(_require_admin), ): """管理员创建新用户""" if db.query(User).filter(User.username == data.username).first(): raise HTTPException(status_code=409, detail="用户名已存在") if db.query(User).filter(User.email == data.email).first(): raise HTTPException(status_code=409, detail="邮箱已存在") user = User( id=str(uuid.uuid4()), username=data.username, email=data.email, password_hash=get_password_hash(data.password), role=data.role, phone=data.phone, status=data.status, ) db.add(user) db.commit() db.refresh(user) logger.info("管理员创建了用户: %s (%s)", user.username, user.id) return {**{c.name: getattr(user, c.name) for c in User.__table__.columns}, "workspace_count": 0} @router.get("/{user_id}", response_model=UserItem) def get_user( user_id: str, db: Session = Depends(get_db), _admin: User = Depends(_require_admin), ): """获取用户详情""" user = db.query(User).filter(User.id == user_id).first() if not user: raise HTTPException(status_code=404, detail="用户不存在") ws_count = ( db.query(func.count(WorkspaceMembership.workspace_id)) .filter(WorkspaceMembership.user_id == user_id) .scalar() ) or 0 return {**{c.name: getattr(user, c.name) for c in User.__table__.columns}, "workspace_count": ws_count} @router.put("/{user_id}", response_model=UserItem) def update_user( user_id: str, data: UserUpdateRequest, db: Session = Depends(get_db), _admin: User = Depends(_require_admin), ): """编辑用户""" user = db.query(User).filter(User.id == user_id).first() if not user: raise HTTPException(status_code=404, detail="用户不存在") if data.username is not None and data.username != user.username: if db.query(User).filter(User.username == data.username, User.id != user_id).first(): raise HTTPException(status_code=409, detail="用户名已存在") user.username = data.username if data.email is not None and data.email != user.email: if db.query(User).filter(User.email == data.email, User.id != user_id).first(): raise HTTPException(status_code=409, detail="邮箱已存在") user.email = data.email if data.role is not None: user.role = data.role if data.phone is not None: user.phone = data.phone if data.status is not None: if data.status not in ("active", "disabled", "deleted"): raise HTTPException(status_code=400, detail="无效的状态值") user.status = data.status if data.subscription_tier is not None: user.subscription_tier = data.subscription_tier db.commit() db.refresh(user) logger.info("管理员更新了用户: %s (%s)", user.username, user.id) ws_count = ( db.query(func.count(WorkspaceMembership.workspace_id)) .filter(WorkspaceMembership.user_id == user_id) .scalar() ) or 0 return {**{c.name: getattr(user, c.name) for c in User.__table__.columns}, "workspace_count": ws_count} @router.delete("/{user_id}") def delete_user( user_id: str, db: Session = Depends(get_db), _admin: User = Depends(_require_admin), ): """软删除用户(标记为 deleted)""" user = db.query(User).filter(User.id == user_id).first() if not user: raise HTTPException(status_code=404, detail="用户不存在") if user.role == "admin": raise HTTPException(status_code=400, detail="不能删除平台管理员") user.status = "deleted" db.commit() logger.info("管理员软删除了用户: %s (%s)", user.username, user.id) return {"message": "用户已删除"} @router.post("/{user_id}/reset-password") def reset_password( user_id: str, data: ResetPasswordRequest, db: Session = Depends(get_db), _admin: User = Depends(_require_admin), ): """重置用户密码""" user = db.query(User).filter(User.id == user_id).first() if not user: raise HTTPException(status_code=404, detail="用户不存在") user.password_hash = get_password_hash(data.new_password) db.commit() logger.info("管理员重置了用户 %s 的密码", user.username) return {"message": "密码已重置"}