From 8e493ae7fddab27c945fe32bf41625a1de4844de Mon Sep 17 00:00:00 2001 From: qzl Date: Thu, 26 Feb 2026 14:37:51 +0800 Subject: [PATCH] docs: add runtime route documentation and AGENTS.md rule --- AGENTS.md | 8 + backend/tests/e2e/test_auth_flow.py | 61 +++-- docs/runtime/runtime-route.md | 360 ++++++++++++++++++++++++++++ 3 files changed, 398 insertions(+), 31 deletions(-) create mode 100644 docs/runtime/runtime-route.md diff --git a/AGENTS.md b/AGENTS.md index 54f6807..f2eaaf4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 diff --git a/backend/tests/e2e/test_auth_flow.py b/backend/tests/e2e/test_auth_flow.py index b775724..3eab3b6 100644 --- a/backend/tests/e2e/test_auth_flow.py +++ b/backend/tests/e2e/test_auth_flow.py @@ -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"}, ) diff --git a/docs/runtime/runtime-route.md b/docs/runtime/runtime-route.md new file mode 100644 index 0000000..4224bdf --- /dev/null +++ b/docs/runtime/runtime-route.md @@ -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` 字段显示给用户。