One request exercises every layer: JWT auth, quota reservation, a versioned prompt through the LLM gateway, a cost row, quota commit or refund, and the unified envelope. The trivial ping task proves the queue routing and the tk_task transitions before any real long-running task is written. Routes are mounted at the /api/v1 base path of 架构文档 §9.1; /v1/* is rewritten to it at the ASGI layer so both spellings work from one route table.
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_value=snapshot.bonus_value,
|
|
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
|
|
]
|
|
)
|