初始化提交
This commit is contained in:
commit
887c1f449e
963 files changed
+166869
No files matched your search
@@ -0,0 +1,152 @@
|
||||
"""人物 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)
|
||||
Reference in new issue
Block a user