Files
eryao/backend/src/v1/points/router.py
T
ZL-Q 86062d5e78 fix: 修复 packages 接口访问不存在字段导致的运行时错误
- 移除 router 中对 result.region、result.currency、pkg.price 的访问
- 修正 pkg.type.value 为 pkg.type (type 是 Literal 不是 Enum)
- 更新协议文档以反映实际实现
- 新增 Apple IAP 协议文档
- 标记未使用的错误码为 RESERVED
2026-04-28 17:31:24 +08:00

108 lines
3.4 KiB
Python

from __future__ import annotations
from datetime import datetime
from typing import Annotated
from fastapi import APIRouter, Depends, Query
from core.auth.models import CurrentUser
from core.http.errors import ApiProblemError, problem_payload
from v1.points.dependencies import get_points_service
from v1.points.schemas import (
PackagesResponse,
PackageInfo,
PointsBalanceResponse,
LedgerListResponse,
LedgerItem,
)
from v1.points.service import PointsService
from v1.users.dependencies import get_current_user
router = APIRouter(prefix="/points", tags=["points"])
def _parse_cursor(cursor: str | None) -> datetime | None:
if cursor is None:
return None
try:
return datetime.fromisoformat(cursor.replace("Z", "+00:00"))
except ValueError as exc:
raise ApiProblemError(
status_code=422,
detail=problem_payload(
code="POINTS_INVALID_CURSOR",
detail="Points ledger cursor must be an ISO 8601 datetime",
params={"cursor": cursor},
),
) from exc
@router.get("/balance", response_model=PointsBalanceResponse)
async def get_points_balance(
service: Annotated[PointsService, Depends(get_points_service)],
current_user: Annotated[CurrentUser, Depends(get_current_user)],
) -> PointsBalanceResponse:
result = await service.get_points_balance(user_id=current_user.id)
return PointsBalanceResponse(
balance=result.balance,
frozenBalance=result.frozen_balance,
availableBalance=result.available_balance,
runCost=result.run_cost,
canRun=result.can_run,
)
@router.get("/packages", response_model=PackagesResponse)
async def get_available_packages(
service: Annotated[PointsService, Depends(get_points_service)],
current_user: Annotated[CurrentUser, Depends(get_current_user)],
) -> PackagesResponse:
result = await service.get_available_packages(
user_id=current_user.id,
user_email=current_user.email or "",
)
return PackagesResponse(
packages=[
PackageInfo(
productCode=pkg.product_code,
appStoreProductId=pkg.app_store_product_id,
type=pkg.type,
credits=pkg.credits,
isStarter=pkg.is_starter,
starterEligible=pkg.starter_eligible,
sortOrder=pkg.sort_order,
)
for pkg in result.packages
],
)
@router.get("/ledger", response_model=LedgerListResponse)
async def get_points_ledger(
service: Annotated[PointsService, Depends(get_points_service)],
current_user: Annotated[CurrentUser, Depends(get_current_user)],
limit: Annotated[int, Query(ge=1, le=100)] = 20,
cursor: str | None = None,
) -> LedgerListResponse:
items, next_cursor, has_more = await service.get_ledger_list(
user_id=current_user.id,
limit=limit,
cursor=_parse_cursor(cursor),
)
return LedgerListResponse(
items=[
LedgerItem(
id=item.id,
direction=item.direction,
amount=item.amount,
balanceAfter=item.balance_after,
changeType=item.change_type,
createdAt=item.created_at,
)
for item in items
],
nextCursor=next_cursor,
hasMore=has_more,
)