Files
superwork/vscode/src/codeReader/fixtures/consoleRoutes.py
T

195 lines
6.2 KiB
Python

"""用户管理 console (admin) 路由。
提供给管理后台用的用户 CRUD、批量开号、批量删除与启用状态切换接口,
路径前缀 ``/admin/users``, 全部需要管理员权限。
"""
from __future__ import annotations
import uuid
from typing import Annotated, Literal
from fastapi import APIRouter, Depends, Query
from langgraph.checkpoint.base import BaseCheckpointSaver
from luffy_billing_sdk.client import BillingClient
from sqlalchemy.ext.asyncio import AsyncSession
from luffy_agent.acquisition.service import acquisition_service
from luffy_agent.agent.runtime.deps import (
get_agent_pool,
get_checkpointer,
get_session_runner,
)
from luffy_agent.agent.runtime.pool import AgentPool
from luffy_agent.agent.runtime.runner import SessionRunner
from luffy_agent.campaign.triggers import emit_user_registered
from luffy_agent.core.db import get_db
from luffy_agent.core.db.schemas import BulkDeleteFailedItem
from luffy_agent.core.exceptions import Forbidden
from luffy_agent.core.filter.deps import FilterQuery
from luffy_agent.core.params import Int4Path
from luffy_agent.deployment.deps import get_billing_client
from luffy_agent.user.auth.deps import (
ConsolePrincipal,
declares_permission,
require_console_user,
require_permission,
)
from luffy_agent.user.console.rbac.service import (
bind_console_audit_request,
console_rbac_service,
)
from luffy_agent.user.console.schemas import (
ImpersonateRequest,
ImpersonateResponse,
UserBatchCreateItem,
UserBatchCreateRequest,
UserBatchCreateResponse,
UserBulkDeleteRequest,
UserBulkDeleteResponse,
UserCreateRequest,
UserListItem,
UserListResponse,
UserPhoneBackfillConflictItem,
UserPhoneBackfillFilledItem,
UserPhoneBackfillResponse,
UserToggleActiveResponse,
)
from luffy_agent.user.models import User, UserRole
from luffy_agent.user.schemas import UserDetailResponse, UserUpdateRequest
from luffy_agent.user.services import BatchUserResult, user_service
router = APIRouter(tags=["用户管理-运维"], prefix="/admin/users")
def _assert_permission(principal: ConsolePrincipal, key: str) -> None:
if not principal.is_root and key not in principal.permissions:
raise Forbidden("无操作权限")
def _reject_admin_role(role: UserRole) -> None:
if role == UserRole.ADMIN:
raise Forbidden("不能设置管理员角色")
def _to_detail(user: User) -> UserDetailResponse:
return UserDetailResponse(
id=user.id,
username=user.username,
display_name=user.display_name,
role=user.role.value,
email=user.email,
phone=user.phone,
bio=user.bio,
avatar_url=user.avatar_url,
is_active=user.is_active,
created_at=user.created_at,
updated_at=user.updated_at,
)
@declares_permission("users:activate")
async def require_toggle_active_permission(
user_id: int,
db: AsyncSession = Depends(get_db),
principal: ConsolePrincipal = Depends(require_console_user),
) -> ConsolePrincipal:
user = await user_service.get_user(db, user_id=user_id)
key = "users:deactivate" if user.is_active else "users:activate"
_assert_permission(principal, key)
return principal
require_users_view = require_permission("users:view")
require_users_create = require_permission("users:create")
require_users_batch_create = require_permission("users:batch_create")
require_users_update = require_permission("users:update")
require_users_delete = require_permission("users:delete")
require_users_bulk_delete = require_permission("users:bulk_delete")
require_users_impersonate = require_permission("users:impersonate")
@router.get("", response_model=UserListResponse)
async def list_users(
filters: FilterQuery,
page: Annotated[int, Query(ge=1, description="页码")] = 1,
page_size: Annotated[int, Query(ge=1, le=100, description="每页数量")] = 20,
acquisition_channel_id: Annotated[
int | None, Query(description="按来源渠道 ID 精确筛选")
] = None,
referred: Annotated[
Literal["yes", "no"] | None,
Query(description="按是否曾被邀请筛选: yes=曾受邀 / no=从未受邀"),
] = None,
db: AsyncSession = Depends(get_db),
_: object = Depends(require_users_view),
) -> UserListResponse:
"""获取用户列表(支持筛选)。
需要管理员权限。
"""
skip = (page - 1) * page_size
rows, total = await user_service.get_admin_users(
db,
skip=skip,
limit=page_size,
filters=filters.filters,
acquisition_channel_id=acquisition_channel_id,
referred=None if referred is None else referred == "yes",
)
return UserListResponse(
items=[
UserListItem(
id=row.user.id,
username=row.user.username,
display_name=row.user.display_name,
role=row.user.role.value,
phone=row.user.phone,
is_active=row.user.is_active,
acquisition_channel_id=row.channel.id,
acquisition_channel_code=row.channel.code,
acquisition_channel_name=row.channel.name,
referred=row.referred,
created_at=row.user.created_at,
updated_at=row.user.updated_at,
)
for row in rows
],
total=total,
page=page,
page_size=page_size,
)
@router.post("", response_model=UserDetailResponse, status_code=201)
async def create_user(
body: UserCreateRequest,
db: AsyncSession = Depends(get_db),
_: object = Depends(require_users_create),
) -> UserDetailResponse:
"""创建新用户。
需要管理员权限。密码会自动哈希。
"""
_reject_admin_role(body.role)
admin_channel_id = await acquisition_service.resolve_registration_channel_id(
db,
created_via="password",
channel_code=None,
admin_created=True,
)
user = await user_service.create_user(
db,
username=body.username,
password=body.password,
display_name=body.display_name,
acquisition_channel_id=admin_channel_id,
role=body.role,
is_active=body.is_active,
)
emit_user_registered(user.id)
return _to_detail(user)