feat(api): vertical slice POST /v1/agent/chat and the Celery ping path

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.
This commit is contained in:
xsl
2026-08-14 00:26:39 +08:00
parent 71204affab
commit 746b66da01
16 changed files with 822 additions and 0 deletions
+50
View File
@@ -0,0 +1,50 @@
"""Development-only token issuance.
**This is not the V1 auth design.** FR-ACC-01/04/05 require 华为账号登录、手机号
验证码、双令牌与服务端可撤销的 refresh token. This endpoint exists so the
vertical slice can be exercised end to end, and it refuses to run unless
``DEV_AUTH_ENABLED`` is true and the environment is local/dev.
"""
from __future__ import annotations
from fastapi import APIRouter
from apps.api.deps import IdentityServiceDep, SettingsDep
from apps.api.schemas import DevTokenRequest, DevTokenResponse
from packages.identity.service import DEV_PROVIDER
from packages.shared.errors import AppError, ErrorCode
from packages.shared.response import ok
from packages.shared.security import create_access_token
router = APIRouter(prefix="/auth", tags=["auth"])
@router.post("/dev-token", summary="[dev only] Issue an access token for testing")
async def dev_token(
payload: DevTokenRequest,
settings: SettingsDep,
identity: IdentityServiceDep,
) -> dict[str, object]:
if not (settings.dev_auth_enabled and settings.is_local):
raise AppError(ErrorCode.NOT_FOUND)
user = await identity.get_or_create_by_external_identity(
provider=DEV_PROVIDER,
union_id=payload.device_key,
register_source="dev",
)
token = create_access_token(
user_id=user.user_id,
secret=settings.jwt_secret,
algorithm=settings.jwt_algorithm,
ttl_seconds=settings.jwt_access_ttl_seconds,
issuer=settings.jwt_issuer,
)
return ok(
DevTokenResponse(
access_token=token,
expires_in=settings.jwt_access_ttl_seconds,
user_id=str(user.user_id),
).model_dump()
)