Files
hunjing/backend/app/api/v1/endpoints/characters.py
T
2026-08-21 16:00:13 +08:00

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)