From 35f91a7d03ed53c0d9aa8aaef631c0926f3ac449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E6=BD=AE?= Date: Sun, 1 Feb 2026 21:48:23 +0800 Subject: [PATCH] admin share --- routes/bot_manager.py | 465 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 464 insertions(+), 1 deletion(-) diff --git a/routes/bot_manager.py b/routes/bot_manager.py index 786af64..2a599c2 100644 --- a/routes/bot_manager.py +++ b/routes/bot_manager.py @@ -144,6 +144,34 @@ async def get_user_id_from_token(authorization: Optional[str]) -> Optional[str]: # ============== 权限检查辅助函数 ============== +async def is_admin_user(authorization: Optional[str]) -> bool: + """ + 检查当前请求是否来自管理员(admin token 或 is_admin=True 的用户) + + Args: + authorization: Authorization header 值 + + Returns: + bool: 是否是管理员 + """ + admin_valid, _ = await verify_admin_auth(authorization) + if admin_valid: + return True + + user_valid, user_id, _ = await verify_user_auth(authorization) + if not user_valid or not user_id: + return False + + pool = get_db_pool_manager().pool + async with pool.connection() as conn: + async with conn.cursor() as cursor: + await cursor.execute(""" + SELECT is_admin FROM agent_user WHERE id = %s + """, (user_id,)) + row = await cursor.fetchone() + return row and row[0] + + async def check_bot_access(bot_id: str, user_id: str, required_permission: str) -> bool: """ 检查用户对 Bot 的访问权限 @@ -2246,7 +2274,7 @@ async def search_users( Returns: List[UserSearchResponse]: 用户列表 """ - # 支持管理员认证和用户认证 + # 支持管理员认证��用户认证 admin_valid, _ = await verify_admin_auth(authorization) user_valid, user_id, _ = await verify_user_auth(authorization) @@ -2295,6 +2323,441 @@ async def search_users( ] +# ============== 用户管理 API(仅管理员)============= + +class UserListResponse(BaseModel): + """用户列表响应""" + id: str + username: str + email: Optional[str] = None + is_admin: bool = False + is_active: bool = True + created_at: str + last_login: Optional[str] = None + + +class UserCreateRequest(BaseModel): + """创建用户请求""" + email: str + username: Optional[str] = None + password: str + is_admin: bool = False + + +class UserUpdateRequest(BaseModel): + """更新用户请求""" + email: Optional[str] = None + username: Optional[str] = None + + +class ResetPasswordRequest(BaseModel): + """重置密码请求""" + new_password: str + + +@router.get("/api/v1/users/list", response_model=List[UserListResponse]) +async def get_all_users(authorization: Optional[str] = Header(None)): + """ + 获取所有用户列表(��管理员) + + Args: + authorization: Bearer token + + Returns: + List[UserListResponse]: 用户列表 + """ + if not await is_admin_user(authorization): + raise HTTPException( + status_code=403, + detail="Admin access required" + ) + + pool = get_db_pool_manager().pool + + async with pool.connection() as conn: + async with conn.cursor() as cursor: + await cursor.execute(""" + SELECT id, username, email, is_admin, is_active, created_at, last_login + FROM agent_user + ORDER BY created_at DESC + """) + rows = await cursor.fetchall() + + return [ + UserListResponse( + id=str(row[0]), + username=row[1], + email=row[2], + is_admin=row[3] or False, + is_active=row[4], + created_at=row[5].isoformat() if row[5] else "", + last_login=row[6].isoformat() if row[6] else None + ) + for row in rows + ] + + +@router.get("/api/v1/users/{user_id}", response_model=UserListResponse) +async def get_user(user_id: str, authorization: Optional[str] = Header(None)): + """ + 获取单个用户信息(仅管理员) + + Args: + user_id: 用户 ID + authorization: Bearer token + + Returns: + UserListResponse: 用户信息 + """ + if not await is_admin_user(authorization): + raise HTTPException( + status_code=403, + detail="Admin access required" + ) + + pool = get_db_pool_manager().pool + + async with pool.connection() as conn: + async with conn.cursor() as cursor: + await cursor.execute(""" + SELECT id, username, email, is_admin, is_active, created_at, last_login + FROM agent_user + WHERE id = %s + """, (user_id,)) + row = await cursor.fetchone() + + if not row: + raise HTTPException( + status_code=404, + detail="User not found" + ) + + return UserListResponse( + id=str(row[0]), + username=row[1], + email=row[2], + is_admin=row[3] or False, + is_active=row[4], + created_at=row[5].isoformat() if row[5] else "", + last_login=row[6].isoformat() if row[6] else None + ) + + +@router.post("/api/v1/users", response_model=UserListResponse) +async def create_user( + request: UserCreateRequest, + authorization: Optional[str] = Header(None) +): + """ + 创建新用户(仅管理员) + + Args: + request: 创建用户请求 + authorization: Bearer token + + Returns: + UserListResponse: 创建的用户信息 + """ + if not await is_admin_user(authorization): + raise HTTPException( + status_code=403, + detail="Admin access required" + ) + + pool = get_db_pool_manager().pool + + async with pool.connection() as conn: + async with conn.cursor() as cursor: + # 如果提供了用户名,检查是否已存在 + if request.username: + await cursor.execute(""" + SELECT id FROM agent_user WHERE username = %s + """, (request.username,)) + if await cursor.fetchone(): + raise HTTPException( + status_code=400, + detail="用户名已存在" + ) + + # 检查邮箱是否已存在 + await cursor.execute(""" + SELECT id FROM agent_user WHERE email = %s + """, (request.email,)) + if await cursor.fetchone(): + raise HTTPException( + status_code=400, + detail="邮箱已被注册" + ) + + # 创建用户 + password_hash = hash_password(request.password) + username = request.username or request.email.split('@')[0] + + await cursor.execute(""" + INSERT INTO agent_user (username, email, password_hash, is_admin) + VALUES (%s, %s, %s, %s) + RETURNING id, created_at + """, (username, request.email, password_hash, request.is_admin)) + user_id, created_at = await cursor.fetchone() + + await conn.commit() + + return UserListResponse( + id=str(user_id), + username=username, + email=request.email, + is_admin=request.is_admin, + is_active=True, + created_at=created_at.isoformat() if created_at else "", + last_login=None + ) + + +@router.put("/api/v1/users/{user_id}", response_model=UserListResponse) +async def update_user( + user_id: str, + request: UserUpdateRequest, + authorization: Optional[str] = Header(None) +): + """ + 更新用户信息(仅管理员) + + Args: + user_id: 用户 ID + request: 更新请求 + authorization: Bearer token + + Returns: + UserListResponse: 更新后的用户信息 + """ + if not await is_admin_user(authorization): + raise HTTPException( + status_code=403, + detail="Admin access required" + ) + + pool = get_db_pool_manager().pool + + async with pool.connection() as conn: + async with conn.cursor() as cursor: + # 构建更新字段 + update_fields = [] + values = [] + + if request.username is not None: + # 检查用户名是否已被其他用户使用 + await cursor.execute(""" + SELECT id FROM agent_user WHERE username = %s AND id != %s + """, (request.username, user_id)) + if await cursor.fetchone(): + raise HTTPException( + status_code=400, + detail="用户名已存在" + ) + update_fields.append("username = %s") + values.append(request.username) + + if request.email is not None: + # 检查邮箱是否已被其他用户使用 + await cursor.execute(""" + SELECT id FROM agent_user WHERE email = %s AND id != %s + """, (request.email, user_id)) + if await cursor.fetchone(): + raise HTTPException( + status_code=400, + detail="邮箱已被使用" + ) + update_fields.append("email = %s") + values.append(request.email) + + if not update_fields: + raise HTTPException( + status_code=400, + detail="No fields to update" + ) + + update_fields.append("updated_at = NOW()") + values.append(user_id) + + await cursor.execute(f""" + UPDATE agent_user + SET {', '.join(update_fields)} + WHERE id = %s + RETURNING id, username, email, is_admin, is_active, created_at, last_login + """, values) + row = await cursor.fetchone() + + if not row: + raise HTTPException( + status_code=404, + detail="User not found" + ) + + await conn.commit() + + return UserListResponse( + id=str(row[0]), + username=row[1], + email=row[2], + is_admin=row[3] or False, + is_active=row[4], + created_at=row[5].isoformat() if row[5] else "", + last_login=row[6].isoformat() if row[6] else None + ) + + +@router.delete("/api/v1/users/{user_id}", response_model=SuccessResponse) +async def delete_user(user_id: str, authorization: Optional[str] = Header(None)): + """ + 删除用户(仅管理员) + + Args: + user_id: 用户 ID + authorization: Bearer token + + Returns: + SuccessResponse: 删除结果 + """ + if not await is_admin_user(authorization): + raise HTTPException( + status_code=403, + detail="Admin access required" + ) + + # 不允许删除默认 admin 用户 + if user_id == '00000000-0000-0000-0000-000000000001': + raise HTTPException( + status_code=400, + detail="Cannot delete default admin user" + ) + + pool = get_db_pool_manager().pool + + async with pool.connection() as conn: + async with conn.cursor() as cursor: + await cursor.execute("DELETE FROM agent_user WHERE id = %s RETURNING username", (user_id,)) + row = await cursor.fetchone() + + if not row: + raise HTTPException( + status_code=404, + detail="User not found" + ) + + await conn.commit() + + return SuccessResponse( + success=True, + message=f"User '{row[0]}' deleted successfully" + ) + + +@router.patch("/api/v1/users/{user_id}/toggle-admin", response_model=SuccessResponse) +async def toggle_user_admin(user_id: str, authorization: Optional[str] = Header(None)): + """ + 切换用户管理员状态(仅管理员) + + Args: + user_id: 用户 ID + authorization: Bearer token + + Returns: + SuccessResponse: 操作结果 + """ + if not await is_admin_user(authorization): + raise HTTPException( + status_code=403, + detail="Admin access required" + ) + + # 不允许取消默认 admin 的管理员权限 + if user_id == '00000000-0000-0000-0000-000000000001': + raise HTTPException( + status_code=400, + detail="Cannot modify default admin user" + ) + + pool = get_db_pool_manager().pool + + async with pool.connection() as conn: + async with conn.cursor() as cursor: + await cursor.execute(""" + UPDATE agent_user + SET is_admin = NOT is_admin, + updated_at = NOW() + WHERE id = %s + RETURNING is_admin, username + """, (user_id,)) + row = await cursor.fetchone() + + if not row: + raise HTTPException( + status_code=404, + detail="User not found" + ) + + new_status, username = row + + await conn.commit() + + return SuccessResponse( + success=True, + message=f"User '{username}' is now {'an admin' if new_status else 'a regular user'}" + ) + + +@router.post("/api/v1/users/{user_id}/reset-password", response_model=SuccessResponse) +async def reset_user_password( + user_id: str, + request: ResetPasswordRequest, + authorization: Optional[str] = Header(None) +): + """ + 重置用户密码(仅管理员) + + Args: + user_id: 用户 ID + request: 重置密码请求 + authorization: Bearer token + + Returns: + SuccessResponse: 操作结果 + """ + if not await is_admin_user(authorization): + raise HTTPException( + status_code=403, + detail="Admin access required" + ) + + pool = get_db_pool_manager().pool + + async with pool.connection() as conn: + async with conn.cursor() as cursor: + password_hash = hash_password(request.new_password) + + await cursor.execute(""" + UPDATE agent_user + SET password_hash = %s, + updated_at = NOW() + WHERE id = %s + RETURNING username + """, (password_hash, user_id)) + row = await cursor.fetchone() + + if not row: + raise HTTPException( + status_code=404, + detail="User not found" + ) + + await conn.commit() + + return SuccessResponse( + success=True, + message=f"Password reset for user '{row[0]}'" + ) + + @router.post("/api/v1/auth/logout", response_model=SuccessResponse) async def user_logout(authorization: Optional[str] = Header(None)): """