docs: add runtime route documentation and AGENTS.md rule
This commit is contained in:
@@ -25,3 +25,11 @@ docker compose --env-file .env -f infra/docker/docker-compose.yml up -d
|
||||
- New development worktrees must be created from `dev` (never from `main`).
|
||||
- Do not develop or commit directly on `main` outside explicit release/merge workflows.
|
||||
- Do not rewrite `main` history unless explicitly requested (including reset and force push).
|
||||
|
||||
## API Route Documentation
|
||||
|
||||
When modifying HTTP routes (adding, updating, or removing endpoints):
|
||||
|
||||
- Sync changes to `docs/runtime/runtime-route.md`
|
||||
- Include: HTTP method, path, request/response schema, status codes, error format
|
||||
- Keep documentation in sync with actual implementation
|
||||
|
||||
@@ -11,15 +11,14 @@ import uvicorn
|
||||
from app import app
|
||||
from v1.auth.dependencies import get_auth_service
|
||||
from v1.auth.schemas import (
|
||||
AuthResendCodeResponse,
|
||||
AuthSignupStartResponse,
|
||||
AuthTokenResponse,
|
||||
AuthUser,
|
||||
LoginRequest,
|
||||
RefreshRequest,
|
||||
SignupResendRequest,
|
||||
SignupStartRequest,
|
||||
SignupVerifyRequest,
|
||||
SessionCreateRequest,
|
||||
SessionRefreshRequest,
|
||||
SessionResponse,
|
||||
VerificationCreateRequest,
|
||||
VerificationCreateResponse,
|
||||
VerificationResendRequest,
|
||||
VerificationVerifyRequest,
|
||||
)
|
||||
from v1.auth.service import AuthService
|
||||
|
||||
@@ -28,13 +27,15 @@ class FakeE2EAuthService(AuthService):
|
||||
def __init__(self) -> None:
|
||||
self._user = AuthUser(id="user-1", email="user@example.com")
|
||||
|
||||
async def signup_start(
|
||||
self, request: SignupStartRequest
|
||||
) -> AuthSignupStartResponse:
|
||||
return AuthSignupStartResponse(email=request.email)
|
||||
async def create_verification(
|
||||
self, request: VerificationCreateRequest
|
||||
) -> VerificationCreateResponse:
|
||||
return VerificationCreateResponse(email=request.email)
|
||||
|
||||
async def signup_verify(self, request: SignupVerifyRequest) -> AuthTokenResponse:
|
||||
return AuthTokenResponse(
|
||||
async def verify_verification(
|
||||
self, request: VerificationVerifyRequest
|
||||
) -> SessionResponse:
|
||||
return SessionResponse(
|
||||
access_token="access-1",
|
||||
refresh_token="refresh-1",
|
||||
expires_in=3600,
|
||||
@@ -42,13 +43,11 @@ class FakeE2EAuthService(AuthService):
|
||||
user=self._user,
|
||||
)
|
||||
|
||||
async def signup_resend(
|
||||
self, request: SignupResendRequest
|
||||
) -> AuthResendCodeResponse:
|
||||
return AuthResendCodeResponse()
|
||||
async def resend_verification(self, request: VerificationResendRequest) -> None:
|
||||
return None
|
||||
|
||||
async def login(self, request: LoginRequest) -> AuthTokenResponse:
|
||||
return AuthTokenResponse(
|
||||
async def create_session(self, request: SessionCreateRequest) -> SessionResponse:
|
||||
return SessionResponse(
|
||||
access_token="access-2",
|
||||
refresh_token="refresh-2",
|
||||
expires_in=3600,
|
||||
@@ -56,8 +55,8 @@ class FakeE2EAuthService(AuthService):
|
||||
user=self._user,
|
||||
)
|
||||
|
||||
async def refresh(self, request: RefreshRequest) -> AuthTokenResponse:
|
||||
return AuthTokenResponse(
|
||||
async def refresh_session(self, request: SessionRefreshRequest) -> SessionResponse:
|
||||
return SessionResponse(
|
||||
access_token="access-3",
|
||||
refresh_token="refresh-3",
|
||||
expires_in=3600,
|
||||
@@ -65,7 +64,7 @@ class FakeE2EAuthService(AuthService):
|
||||
user=self._user,
|
||||
)
|
||||
|
||||
async def logout(self, refresh_token: str | None) -> None:
|
||||
async def delete_session(self, refresh_token: str | None) -> None:
|
||||
return None
|
||||
|
||||
|
||||
@@ -106,8 +105,8 @@ def test_auth_flow_e2e() -> None:
|
||||
base_url=f"http://{host}:{port}"
|
||||
)
|
||||
try:
|
||||
signup = request_context.post(
|
||||
"/api/v1/auth/signup/start",
|
||||
verification = request_context.post(
|
||||
"/api/v1/auth/verifications",
|
||||
data=json.dumps(
|
||||
{
|
||||
"username": "demo",
|
||||
@@ -117,10 +116,10 @@ def test_auth_flow_e2e() -> None:
|
||||
),
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
assert signup.status == 202
|
||||
assert verification.status == 202
|
||||
|
||||
verify = request_context.post(
|
||||
"/api/v1/auth/signup/verify",
|
||||
"/api/v1/auth/verifications/verify",
|
||||
data=json.dumps(
|
||||
{
|
||||
"email": "user@example.com",
|
||||
@@ -133,7 +132,7 @@ def test_auth_flow_e2e() -> None:
|
||||
assert verify.json()["access_token"] == "access-1"
|
||||
|
||||
login = request_context.post(
|
||||
"/api/v1/auth/login",
|
||||
"/api/v1/auth/sessions",
|
||||
data=json.dumps(
|
||||
{"email": "user@example.com", "password": "secret123"}
|
||||
),
|
||||
@@ -143,15 +142,15 @@ def test_auth_flow_e2e() -> None:
|
||||
assert login.json()["access_token"] == "access-2"
|
||||
|
||||
refresh = request_context.post(
|
||||
"/api/v1/auth/refresh",
|
||||
"/api/v1/auth/sessions/refresh",
|
||||
data=json.dumps({"refresh_token": "refresh-2"}),
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
assert refresh.status == 200
|
||||
assert refresh.json()["access_token"] == "access-3"
|
||||
|
||||
logout = request_context.post(
|
||||
"/api/v1/auth/logout",
|
||||
logout = request_context.delete(
|
||||
"/api/v1/auth/sessions",
|
||||
data=json.dumps({"refresh_token": "refresh-3"}),
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
|
||||
@@ -0,0 +1,360 @@
|
||||
# Runtime API Routes
|
||||
|
||||
本文档记录所有 HTTP API 端点。修改路由时必须同步更新此文档。
|
||||
|
||||
## 格式说明
|
||||
|
||||
- Request/Response 使用 JSON 格式
|
||||
- 错误响应使用 RFC 7807 `application/problem+json`
|
||||
- 所有端点前缀: `/api/v1`
|
||||
|
||||
## Auth
|
||||
|
||||
### POST /auth/verifications
|
||||
|
||||
创建验证码(注册发起)。
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"username": "string (3-30 chars)",
|
||||
"email": "string (email)",
|
||||
"password": "string (min 6 chars)",
|
||||
"redirect_to": "string? (optional)"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:** 202 Accepted
|
||||
```json
|
||||
{
|
||||
"email": "user@example.com"
|
||||
}
|
||||
```
|
||||
|
||||
**Errors:**
|
||||
- 422: 请求参数无效
|
||||
- 429: 请求过于频繁
|
||||
|
||||
---
|
||||
|
||||
### POST /auth/verifications/resend
|
||||
|
||||
重发验证码。
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"email": "string (email)"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:** 204 No Content
|
||||
|
||||
**Errors:**
|
||||
- 422: 请求参数无效
|
||||
- 429: 请求过于频繁
|
||||
|
||||
---
|
||||
|
||||
### POST /auth/verifications/verify
|
||||
|
||||
验证码校验。
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"email": "string (email)",
|
||||
"token": "string (6 digits)"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:** 200 OK
|
||||
```json
|
||||
{
|
||||
"access_token": "string",
|
||||
"refresh_token": "string",
|
||||
"expires_in": 3600,
|
||||
"token_type": "bearer",
|
||||
"user": {
|
||||
"id": "string",
|
||||
"email": "string"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Errors:**
|
||||
- 401: 验证码无效或已过期
|
||||
- 422: 请求参数无效
|
||||
- 429: 请求过于频繁
|
||||
|
||||
---
|
||||
|
||||
### POST /auth/sessions
|
||||
|
||||
登录(创建会话)。
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"email": "string (email)",
|
||||
"password": "string (min 6 chars)"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:** 200 OK
|
||||
```json
|
||||
{
|
||||
"access_token": "string",
|
||||
"refresh_token": "string",
|
||||
"expires_in": 3600,
|
||||
"token_type": "bearer",
|
||||
"user": {
|
||||
"id": "string",
|
||||
"email": "string"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Errors:**
|
||||
- 401: 邮箱或密码错误
|
||||
- 422: 请求参数无效
|
||||
- 429: 请求过于频繁
|
||||
|
||||
---
|
||||
|
||||
### POST /auth/sessions/refresh
|
||||
|
||||
刷新 Token。
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"refresh_token": "string"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:** 200 OK
|
||||
```json
|
||||
{
|
||||
"access_token": "string",
|
||||
"refresh_token": "string",
|
||||
"expires_in": 3600,
|
||||
"token_type": "bearer",
|
||||
"user": {
|
||||
"id": "string",
|
||||
"email": "string"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Errors:**
|
||||
- 401: 无效的 refresh token
|
||||
- 422: 请求参数无效
|
||||
|
||||
---
|
||||
|
||||
### DELETE /auth/sessions
|
||||
|
||||
登出(删除会话)。
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"refresh_token": "string"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:** 204 No Content
|
||||
|
||||
**Errors:**
|
||||
- 422: 请求参数无效
|
||||
|
||||
---
|
||||
|
||||
### GET /auth/users
|
||||
|
||||
按邮箱查询用户(需要认证)。
|
||||
|
||||
**Query Parameters:**
|
||||
- `email`: string (required)
|
||||
|
||||
**Response:** 200 OK
|
||||
```json
|
||||
{
|
||||
"id": "string",
|
||||
"email": "string",
|
||||
"created_at": "string (ISO 8601)",
|
||||
"email_confirmed_at": "string? (ISO 8601)"
|
||||
}
|
||||
```
|
||||
|
||||
**Errors:**
|
||||
- 403: 无权限访问
|
||||
- 404: 用户不存在
|
||||
- 422: 请求参数无效
|
||||
|
||||
---
|
||||
|
||||
## Users
|
||||
|
||||
### GET /users/me
|
||||
|
||||
获取当前用户信息(需要认证)。
|
||||
|
||||
**Response:** 200 OK
|
||||
```json
|
||||
{
|
||||
"id": "string",
|
||||
"username": "string",
|
||||
"avatar_url": "string?",
|
||||
"bio": "string?"
|
||||
}
|
||||
```
|
||||
|
||||
**Errors:**
|
||||
- 401: 未认证
|
||||
|
||||
---
|
||||
|
||||
### PATCH /users/me
|
||||
|
||||
更新当前用户信息(需要认证)。
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"username": "string? (3-30 chars)",
|
||||
"avatar_url": "string? (URL)",
|
||||
"bio": "string? (max 200 chars)"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:** 200 OK
|
||||
```json
|
||||
{
|
||||
"id": "string",
|
||||
"username": "string",
|
||||
"avatar_url": "string?",
|
||||
"bio": "string?"
|
||||
}
|
||||
```
|
||||
|
||||
**Errors:**
|
||||
- 401: 未认证
|
||||
- 422: 请求参数无效
|
||||
|
||||
---
|
||||
|
||||
### GET /users/{username}
|
||||
|
||||
按用户名查询用户(需要认证)。
|
||||
|
||||
**Path Parameters:**
|
||||
- `username`: string (3-30 chars, alphanumeric and underscore)
|
||||
|
||||
**Response:** 200 OK
|
||||
```json
|
||||
{
|
||||
"id": "string",
|
||||
"username": "string",
|
||||
"avatar_url": "string?",
|
||||
"bio": "string?"
|
||||
}
|
||||
```
|
||||
|
||||
**Errors:**
|
||||
- 401: 未认证
|
||||
- 404: 用户不存在
|
||||
- 422: 请求参数无效
|
||||
|
||||
---
|
||||
|
||||
## Agent Chat
|
||||
|
||||
### POST /agent-chats
|
||||
|
||||
运行 Agent 对话(需要认证)。
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"message": "string (1-8000 chars)",
|
||||
"session_id": "string? (UUID)"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:** 200 OK
|
||||
```json
|
||||
{
|
||||
"session_id": "string (UUID)",
|
||||
"output": "string",
|
||||
"events": [
|
||||
{
|
||||
"type": "string",
|
||||
"run_id": "string?",
|
||||
"message_id": "string?",
|
||||
"delta": "string?",
|
||||
"tool_name": "string?",
|
||||
"result": "string?",
|
||||
"output": "string?",
|
||||
"error": "string?"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Errors:**
|
||||
- 401: 未认证
|
||||
- 422: 请求参数无效
|
||||
|
||||
---
|
||||
|
||||
## Infra
|
||||
|
||||
### GET /infra/health
|
||||
|
||||
检查基础设施健康状态。
|
||||
|
||||
**Response:** 200 OK
|
||||
```json
|
||||
{
|
||||
"status": "healthy" | "unhealthy",
|
||||
"services": {
|
||||
"redis": {
|
||||
"status": "healthy" | "unhealthy",
|
||||
"latency_ms": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET /health
|
||||
|
||||
检查服务健康状态。
|
||||
|
||||
**Response:** 200 OK
|
||||
```json
|
||||
{
|
||||
"status": "ok"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Response Format (RFC 7807)
|
||||
|
||||
所有错误响应使用 `application/problem+json` 格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Unauthorized",
|
||||
"status": 401,
|
||||
"detail": "验证码无效或已过期",
|
||||
"instance": "/api/v1/auth/verifications/verify"
|
||||
}
|
||||
```
|
||||
|
||||
前端应优先读取 `detail` 字段显示给用户。
|
||||
Reference in New Issue
Block a user