"""素材路由(FR-ADM-03):图像资产登记与审核。 素材由运营流水线高频登记(T-P2-07/15),审核是唯一能把 ``tryon_ready`` 打开的入口——服务层已强制「tryon_ready 只能随 approved 打开」,这里只做 透传,约束在 HTTP 层的表现是 400(BAD_REQUEST)。 """ from __future__ import annotations from typing import Any from fastapi import APIRouter, Depends, Query from apps.admin.deps import KbAssetServiceDep, admin_auth from packages.kb.schemas import AssetCreate, AssetOut, AssetReview from packages.shared.models import KbAsset from packages.shared.response import ok router = APIRouter(prefix="/assets", tags=["admin:assets"], dependencies=[Depends(admin_auth)]) def _serialize(asset: KbAsset) -> dict[str, Any]: return AssetOut( asset_id=str(asset.asset_id), entity_id=str(asset.entity_id), asset_type=asset.asset_type, cos_key=asset.cos_key, spec_check=dict(asset.spec_check), tryon_ready=asset.tryon_ready, review_status=asset.review_status, license=asset.license, created_at=asset.created_at, ).model_dump() @router.post("", summary="登记素材(登记时 tryon_ready 恒为 False,与请求体取值无关)") async def register_asset(payload: AssetCreate, assets: KbAssetServiceDep) -> dict[str, Any]: asset = await assets.register_asset(payload) return ok(_serialize(asset)) @router.get("", summary="素材列表:entity_id / tryon_ready 过滤 + 分页") async def list_assets( assets: KbAssetServiceDep, entity_id: int | None = Query(default=None, description="按实体过滤(雪花 ID)"), tryon_ready: bool | None = Query(default=None, description="true=仅可换装素材"), limit: int = Query(default=50, ge=1, le=200), offset: int = Query(default=0, ge=0), ) -> dict[str, Any]: rows, total = await assets.list_assets( entity_id=entity_id, tryon_ready=tryon_ready, limit=limit, offset=offset ) return ok( { "items": [_serialize(row) for row in rows], "total": total, "limit": limit, "offset": offset, } ) @router.post( "/{asset_id}/review", summary="审核素材(tryon_ready=true 仅在 review_status=approved 时被接受)", ) async def review_asset( asset_id: int, payload: AssetReview, assets: KbAssetServiceDep ) -> dict[str, Any]: asset = await assets.review_asset(asset_id, payload) return ok(_serialize(asset))