The product plan is the only source for quota numbers, but the 0001 seed used a second set (agent_chat 20/50, tryon hard 10) and omitted hairstyle, hd_export and report regen. Migration 0003 replaces the seed and renames points_reserved → quota_reserved and bonus_value → bonus_granted so the docs and the schema stop contradicting each other. Effective limit now clips at hard_cap. CI runs the integration slice against the Postgres/Redis services it already starts.
62 lines
2.0 KiB
Python
Executable File
62 lines
2.0 KiB
Python
Executable File
"""``GET /quota`` and ``GET /quota/ledger`` (架构文档 §9.3 配额).
|
|
|
|
FR-BILL-07: 额度相关文案中**不得出现任何价格、购买、会员、升级字样**。
|
|
Only two recovery paths may ever be surfaced: 等待恢复 and 做任务换额度.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from fastapi import APIRouter, Query
|
|
|
|
from apps.api.deps import BillingServiceDep, CurrentUserId
|
|
from apps.api.schemas import LedgerEntry, QuotaResponse
|
|
from packages.agent.service import QUOTA_KEY_AGENT_CHAT
|
|
from packages.shared.response import ok
|
|
|
|
router = APIRouter(prefix="/quota", tags=["quota"])
|
|
|
|
|
|
@router.get("", summary="Current period quota for a feature")
|
|
async def get_quota(
|
|
user_id: CurrentUserId,
|
|
billing: BillingServiceDep,
|
|
quota_key: str = Query(default=QUOTA_KEY_AGENT_CHAT, max_length=64),
|
|
) -> dict[str, object]:
|
|
snapshot = await billing.get_quota(user_id=user_id, quota_key=quota_key)
|
|
return ok(
|
|
QuotaResponse(
|
|
quota_key=snapshot.quota_key,
|
|
period_key=snapshot.period_key,
|
|
used=snapshot.used,
|
|
limit_value=snapshot.limit_value,
|
|
bonus_granted=snapshot.bonus_granted,
|
|
remaining=snapshot.remaining,
|
|
reset_at=snapshot.reset_at,
|
|
).model_dump()
|
|
)
|
|
|
|
|
|
@router.get("/ledger", summary="Quota ledger — 流水是真相,余额是缓存")
|
|
async def get_ledger(
|
|
user_id: CurrentUserId,
|
|
billing: BillingServiceDep,
|
|
limit: int = Query(default=50, ge=1, le=200),
|
|
) -> dict[str, object]:
|
|
rows = await billing.list_ledger(user_id=user_id, limit=limit)
|
|
return ok(
|
|
[
|
|
LedgerEntry(
|
|
tx_id=str(row.tx_id),
|
|
delta=row.delta,
|
|
tx_type=row.tx_type,
|
|
status=row.status,
|
|
reason=row.reason,
|
|
quota_key=row.quota_key,
|
|
period_key=row.period_key,
|
|
balance_after=row.balance_after,
|
|
created_at=row.created_at,
|
|
).model_dump()
|
|
for row in rows
|
|
]
|
|
)
|