195 lines
6.2 KiB
Python
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)
|
|
|