Files
yu-zhi-ran/platform/backend/app/api/admin.py
T

256 lines
8.3 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, Request
from sqlalchemy.orm import Session
from typing import List
import bcrypt
from typing import Optional
from pydantic import BaseModel
from ..database import get_db
from ..models import User, Topic, SystemConfig
from ..schemas import UserCreate, UserUpdate, UserResponse
from ..core.audit_logger import audit_log
from .auth import get_current_admin, org_filter
import json
router = APIRouter(prefix="/api/admin", tags=["admin"])
@router.get("/users", response_model=List[UserResponse])
def list_users(
request: Request,
db: Session = Depends(get_db),
admin_user: User = Depends(get_current_admin)
):
"""获取用户列表(管理员)"""
q = db.query(User)
of = org_filter(admin_user, User)
if of is not True:
q = q.filter(of)
users = q.all()
return [UserResponse.from_orm(u) for u in users]
@router.post("/users", response_model=UserResponse)
def create_user(
user_data: UserCreate,
request: Request,
db: Session = Depends(get_db),
admin_user: User = Depends(get_current_admin)
):
"""创建新用户(管理员)"""
# 检查用户名是否已存在
existing = db.query(User).filter(User.username == user_data.username).first()
if existing:
raise HTTPException(status_code=400, detail="用户名已存在")
# bcrypt 密码哈希
hashed_password = bcrypt.hashpw(user_data.password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
user = User(
username=user_data.username,
password_hash=hashed_password,
role=user_data.role or "user",
org_id=user_data.org_id or admin_user.org_id or "default"
)
db.add(user)
db.commit()
db.refresh(user)
audit_log(
action="create_user",
user=admin_user,
resource_type="user",
resource_id=str(user.id),
details={"username": user.username, "role": user.role},
ip_address=request.client.host if request.client else None,
user_agent=request.headers.get("user-agent", ""),
db=db
)
return UserResponse.from_orm(user)
@router.put("/users/{user_id}", response_model=UserResponse)
def update_user(
user_id: int,
user_update: UserUpdate,
request: Request,
db: Session = Depends(get_db),
admin_user: User = Depends(get_current_admin)
):
"""更新用户(管理员)"""
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
changes = {}
if user_update.username is not None:
if user_update.username != user.username:
existing = db.query(User).filter(User.username == user_update.username).first()
if existing:
raise HTTPException(status_code=400, detail="用户名已存在")
changes["username"] = {"old": user.username, "new": user_update.username}
user.username = user_update.username
if user_update.password is not None:
changes["password"] = {"changed": True}
user.password_hash = bcrypt.hashpw(user_update.password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
if user_update.role is not None and user_update.role != user.role:
changes["role"] = {"old": user.role, "new": user_update.role}
user.role = user_update.role
db.commit()
db.refresh(user)
audit_log(
action="update_user",
user=admin_user,
resource_type="user",
resource_id=str(user.id),
details=changes,
ip_address=request.client.host if request.client else None,
user_agent=request.headers.get("user-agent", ""),
db=db
)
return UserResponse.from_orm(user)
@router.delete("/users/{user_id}")
def delete_user(
user_id: int,
request: Request,
db: Session = Depends(get_db),
admin_user: User = Depends(get_current_admin)
):
"""删除用户(管理员)"""
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="不能删除管理员用户")
username = user.username # 保存用于日志
db.delete(user)
db.commit()
audit_log(
action="delete_user",
user=admin_user,
resource_type="user",
resource_id=str(user_id),
details={"username": username},
ip_address=request.client.host if request.client else None,
user_agent=request.headers.get("user-agent", ""),
db=db
)
return {"message": "删除成功"}
# ---------- Org Management ----------
class OrgCreate(BaseModel):
org_id: str
name: str
description: Optional[str] = None
class OrgUpdate(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
@router.get("/orgs")
def list_orgs(
db: Session = Depends(get_db),
admin_user: User = Depends(get_current_admin)
):
distinct_orgs = db.query(User.org_id).distinct().all()
result = []
for (org_id,) in distinct_orgs:
if not org_id:
continue
cfg = db.query(SystemConfig).filter(SystemConfig.key == f"org:{org_id}").first()
meta = json.loads(cfg.value) if cfg and cfg.value else {}
user_count = db.query(User).filter(User.org_id == org_id).count()
topic_count = db.query(Topic).filter(Topic.org_id == org_id).count()
result.append({
"org_id": org_id,
"name": meta.get("name", org_id),
"description": meta.get("description", ""),
"user_count": user_count,
"topic_count": topic_count,
"is_default": org_id == "default"
})
return result
@router.post("/orgs")
def create_org(
data: OrgCreate,
request: Request,
db: Session = Depends(get_db),
admin_user: User = Depends(get_current_admin)
):
existing = db.query(SystemConfig).filter(SystemConfig.key == f"org:{data.org_id}").first()
if existing:
raise HTTPException(status_code=400, detail="组织已存在")
cfg = SystemConfig(
key=f"org:{data.org_id}",
value=json.dumps({"name": data.name, "description": data.description or ""}, ensure_ascii=False),
description=f"Organization: {data.org_id}"
)
db.add(cfg)
db.commit()
audit_log(
action="create_org", user=admin_user,
resource_type="org", resource_id=data.org_id,
details=data.model_dump(),
ip_address=request.client.host if request.client else None,
user_agent=request.headers.get("user-agent", ""),
db=db
)
return {"org_id": data.org_id, "name": data.name, "description": data.description or ""}
@router.put("/orgs/{org_id}")
def update_org(
org_id: str,
data: OrgUpdate,
request: Request,
db: Session = Depends(get_db),
admin_user: User = Depends(get_current_admin)
):
cfg = db.query(SystemConfig).filter(SystemConfig.key == f"org:{org_id}").first()
if not cfg:
raise HTTPException(status_code=404, detail="组织不存在")
meta = json.loads(cfg.value) if cfg.value else {}
if data.name is not None:
meta["name"] = data.name
if data.description is not None:
meta["description"] = data.description
cfg.value = json.dumps(meta, ensure_ascii=False)
db.commit()
audit_log(
action="update_org", user=admin_user,
resource_type="org", resource_id=org_id,
details=data.model_dump(exclude_unset=True),
ip_address=request.client.host if request.client else None,
user_agent=request.headers.get("user-agent", ""),
db=db
)
return {"org_id": org_id, **meta}
@router.delete("/orgs/{org_id}")
def delete_org(
org_id: str,
request: Request,
db: Session = Depends(get_db),
admin_user: User = Depends(get_current_admin)
):
if org_id == "default":
raise HTTPException(status_code=400, detail="不能删除默认组织")
cfg = db.query(SystemConfig).filter(SystemConfig.key == f"org:{org_id}").first()
if cfg:
db.delete(cfg)
db.commit()
audit_log(
action="delete_org", user=admin_user,
resource_type="org", resource_id=org_id,
ip_address=request.client.host if request.client else None,
user_agent=request.headers.get("user-agent", ""),
db=db
)
return {"message": "删除成功"}