"""人物 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)