153 lines
5.3 KiB
Python
153 lines
5.3 KiB
Python
"""人物 API。"""
|
|
|
|
from fastapi import APIRouter, Depends, Header, Query, Request
|
|
|
|
from app.api.v1.deps import get_current_user
|
|
from app.core.errors import AppError
|
|
from app.schemas.auth import CurrentUser
|
|
from app.schemas.character import (
|
|
ActivateCharacterResponse,
|
|
CharacterResponse,
|
|
CharacterSummaryResponse,
|
|
CharacterTemplateSummaryResponse,
|
|
CreateCharacterRequest,
|
|
UpdateModelPolicyRequest,
|
|
UpdateProfileRequest,
|
|
)
|
|
from app.schemas.common import Page
|
|
from app.services.characters import (
|
|
activate_character,
|
|
archive_character,
|
|
create_character,
|
|
get_active_character,
|
|
get_character_by_id,
|
|
list_characters_page,
|
|
list_system_templates_page,
|
|
pause_character,
|
|
resume_character,
|
|
update_character_model_policy,
|
|
update_character_profile,
|
|
)
|
|
from app.services.idempotency import run_idempotent
|
|
|
|
router = APIRouter(prefix="/characters", tags=["characters"])
|
|
|
|
|
|
@router.get("/active", response_model=CharacterResponse)
|
|
async def active_character(
|
|
current_user: CurrentUser = Depends(get_current_user),
|
|
) -> CharacterResponse:
|
|
return await get_active_character(owner_scope=current_user.owner_scope)
|
|
|
|
|
|
@router.get("/templates", response_model=list[CharacterTemplateSummaryResponse])
|
|
async def list_templates(
|
|
current_user: CurrentUser = Depends(get_current_user),
|
|
) -> list[CharacterTemplateSummaryResponse]:
|
|
"""列出所有系统模板,仅管理员可见(管理端完整 CRUD 见 ``/admin/characters/templates``)。"""
|
|
if not current_user.is_admin:
|
|
raise AppError("permission_denied", "仅管理员可查看系统模板。")
|
|
return await list_system_templates_page()
|
|
|
|
|
|
@router.get("", response_model=Page[CharacterSummaryResponse])
|
|
async def list_characters(
|
|
page: int = Query(default=1, ge=1),
|
|
page_size: int = Query(default=20, ge=1, le=100),
|
|
status: str | None = Query(default=None, description="按人物状态过滤。"),
|
|
current_user: CurrentUser = Depends(get_current_user),
|
|
) -> Page[CharacterSummaryResponse]:
|
|
return await list_characters_page(
|
|
owner_scope=current_user.owner_scope, status=status, page=page, page_size=page_size
|
|
)
|
|
|
|
|
|
@router.post("", response_model=CharacterResponse)
|
|
async def create_character_api(
|
|
payload: CreateCharacterRequest,
|
|
request: Request,
|
|
idempotency_key: str | None = Header(default=None, alias="Idempotency-Key"),
|
|
current_user: CurrentUser = Depends(get_current_user),
|
|
) -> CharacterResponse:
|
|
"""创建人物。
|
|
|
|
- 传 ``template_key`` 时从系统模板复制(仅管理员可用)。
|
|
- 不传 ``template_key`` 时创建空白人物(任何用户可用,status=wuji)。
|
|
"""
|
|
if payload.template_key and not current_user.is_admin:
|
|
raise AppError("permission_denied", "仅管理员可从系统模板创建人物。")
|
|
return await run_idempotent(
|
|
key=idempotency_key,
|
|
method="POST",
|
|
path=str(request.url.path),
|
|
payload=payload,
|
|
response_type=CharacterResponse,
|
|
resource_type="character",
|
|
handler=lambda session: create_character(
|
|
session, payload, owner_scope=current_user.owner_scope
|
|
),
|
|
owner_scope=current_user.owner_scope,
|
|
)
|
|
|
|
|
|
@router.get("/{character_id}", response_model=CharacterResponse)
|
|
async def get_character(
|
|
character_id: str,
|
|
current_user: CurrentUser = Depends(get_current_user),
|
|
) -> CharacterResponse:
|
|
return await get_character_by_id(character_id, owner_scope=current_user.owner_scope)
|
|
|
|
|
|
@router.patch("/{character_id}/profile", response_model=CharacterResponse)
|
|
async def update_profile(
|
|
character_id: str,
|
|
payload: UpdateProfileRequest,
|
|
current_user: CurrentUser = Depends(get_current_user),
|
|
) -> CharacterResponse:
|
|
return await update_character_profile(
|
|
character_id, payload, owner_scope=current_user.owner_scope
|
|
)
|
|
|
|
|
|
@router.patch("/{character_id}/model-policy", response_model=CharacterResponse)
|
|
async def update_model_policy(
|
|
character_id: str,
|
|
payload: UpdateModelPolicyRequest,
|
|
current_user: CurrentUser = Depends(get_current_user),
|
|
) -> CharacterResponse:
|
|
return await update_character_model_policy(
|
|
character_id, payload, owner_scope=current_user.owner_scope
|
|
)
|
|
|
|
|
|
@router.post("/{character_id}/activate", response_model=ActivateCharacterResponse)
|
|
async def activate(
|
|
character_id: str,
|
|
current_user: CurrentUser = Depends(get_current_user),
|
|
) -> ActivateCharacterResponse:
|
|
return await activate_character(character_id, owner_scope=current_user.owner_scope)
|
|
|
|
|
|
@router.post("/{character_id}/pause", response_model=CharacterResponse)
|
|
async def pause(
|
|
character_id: str,
|
|
current_user: CurrentUser = Depends(get_current_user),
|
|
) -> CharacterResponse:
|
|
return await pause_character(character_id, owner_scope=current_user.owner_scope)
|
|
|
|
|
|
@router.post("/{character_id}/resume", response_model=CharacterResponse)
|
|
async def resume(
|
|
character_id: str,
|
|
current_user: CurrentUser = Depends(get_current_user),
|
|
) -> CharacterResponse:
|
|
return await resume_character(character_id, owner_scope=current_user.owner_scope)
|
|
|
|
|
|
@router.post("/{character_id}/archive", response_model=CharacterResponse)
|
|
async def archive(
|
|
character_id: str,
|
|
current_user: CurrentUser = Depends(get_current_user),
|
|
) -> CharacterResponse:
|
|
return await archive_character(character_id, owner_scope=current_user.owner_scope)
|