Files
social-app/docs/plans/2026-02-26-restful-api-refactor-plan.md
T

1514 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# RESTful API 重构实现计划
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** 将后端 HTTP API 改造为完全符合 RESTful 规范,同步更新前端适配代码,并添加路由文档。
**Architecture:**
- 后端:重命名路由 URL,简化响应模型,profile 模块重命名为 users
- 前端:更新 API 调用路径,简化响应模型
- 文档:新增路由文档,更新 AGENTS.md 规则
**Tech Stack:** FastAPI, Pydantic, Flutter, Dart
---
## Phase 1: 后端 Schema 重构
### Task 1: 重命名和简化 Auth Schema
**Files:**
- Modify: `backend/src/v1/auth/schemas.py`
**Step 1: 更新 schema 文件**
将现有 schema 重命名并简化:
```python
from __future__ import annotations
from typing import Literal
from pydantic import BaseModel, EmailStr, Field
class VerificationCreateRequest(BaseModel):
username: str = Field(min_length=3, max_length=30)
email: EmailStr
password: str = Field(min_length=6)
redirect_to: str | None = None
class VerificationResendRequest(BaseModel):
email: EmailStr
class VerificationVerifyRequest(BaseModel):
email: EmailStr
token: str = Field(pattern=r"^\d{6}$")
class SessionCreateRequest(BaseModel):
email: EmailStr
password: str = Field(min_length=6)
class SessionRefreshRequest(BaseModel):
refresh_token: str = Field(min_length=1)
class SessionDeleteRequest(BaseModel):
refresh_token: str = Field(min_length=1)
class AuthUser(BaseModel):
id: str
email: EmailStr
class SessionResponse(BaseModel):
access_token: str
refresh_token: str
expires_in: int
token_type: str
user: AuthUser
class UserByEmailResponse(BaseModel):
id: str
email: EmailStr
created_at: str
email_confirmed_at: str | None = None
class VerificationCreateResponse(BaseModel):
email: EmailStr
class PasswordResetRequest(BaseModel):
email: EmailStr
redirect_to: str | None = None
class PasswordResetResponse(BaseModel):
message: str = "Password reset email sent"
```
**Step 2: 验证类型检查**
Run: `cd backend && uv run basedpyright src/v1/auth/schemas.py`
Expected: No errors
**Step 3: Commit**
```bash
git add backend/src/v1/auth/schemas.py
git commit -m "refactor(auth): rename and simplify auth schemas for RESTful API"
```
---
### Task 2: 创建 Users Schema
**Files:**
- Create: `backend/src/v1/users/schemas.py`
**Step 1: 创建 users schema 文件**
```python
from __future__ import annotations
from pydantic import (
AnyHttpUrl,
BaseModel,
ConfigDict,
Field,
field_validator,
model_validator,
)
class UserResponse(BaseModel):
id: str
username: str
avatar_url: str | None = None
bio: str | None = None
class UserUpdateRequest(BaseModel):
model_config = ConfigDict(extra="forbid")
username: str | None = Field(default=None, min_length=3, max_length=30)
avatar_url: str | None = Field(default=None)
bio: str | None = Field(default=None, max_length=200)
@field_validator("avatar_url", mode="before")
@classmethod
def validate_avatar_url(cls, v: str | None) -> str | None:
if v is None:
return None
parsed = AnyHttpUrl(v)
if parsed.scheme not in ("http", "https"):
raise ValueError("avatar_url must use http or https scheme")
return str(parsed)
@model_validator(mode="after")
def require_one_field(self) -> "UserUpdateRequest":
if self.username is None and self.avatar_url is None and self.bio is None:
raise ValueError("At least one field must be provided")
return self
```
**Step 2: 验证类型检查**
Run: `cd backend && uv run basedpyright src/v1/users/schemas.py`
Expected: No errors
**Step 3: Commit**
```bash
git add backend/src/v1/users/schemas.py
git commit -m "feat(users): create users schemas"
```
---
### Task 3: 创建 Users 模块基础文件
**Files:**
- Create: `backend/src/v1/users/__init__.py`
- Create: `backend/src/v1/users/dependencies.py`
- Move: `backend/src/v1/profile/repository.py``backend/src/v1/users/repository.py`
- Move: `backend/src/v1/profile/service.py``backend/src/v1/users/service.py`
**Step 1: 创建 __init__.py**
```python
from __future__ import annotations
```
**Step 2: 创建 dependencies.py**
```python
from __future__ import annotations
from typing import Annotated
from fastapi import Depends
from core.auth.models import CurrentUser
from core.db import get_db
from v1.auth.dependencies import get_current_user
from v1.users.repository import UserRepository
from v1.users.service import UserService
async def get_user_repository(
db=Depends(get_db),
) -> UserRepository:
return UserRepository(db)
async def get_user_service(
repo: Annotated[UserRepository, Depends(get_user_repository)],
current_user: Annotated[CurrentUser, Depends(get_current_user)],
) -> UserService:
return UserService(repo, current_user)
```
**Step 3: 移动并更新 repository.py**
复制 `v1/profile/repository.py``v1/users/repository.py`,无需修改内容。
**Step 4: 移动并更新 service.py**
复制 `v1/profile/service.py``v1/users/service.py`,更新导入:
```python
from v1.users.schemas import UserResponse, UserUpdateRequest
```
**Step 5: 验证类型检查**
Run: `cd backend && uv run basedpyright src/v1/users/`
Expected: No errors
**Step 4: Commit**
```bash
git add backend/src/v1/users/
git commit -m "feat(users): create users module from profile"
```
---
## Phase 2: 后端路由重构
### Task 4: 重构 Auth Router
**Files:**
- Modify: `backend/src/v1/auth/router.py`
**Step 1: 更新路由定义**
```python
from __future__ import annotations
from typing import Annotated
from fastapi import APIRouter, Depends, Response
from fastapi import HTTPException
from core.auth.models import CurrentUser
from v1.auth.rate_limit import enforce_rate_limit
from v1.auth.dependencies import get_auth_service
from v1.users.dependencies import get_current_user
from v1.auth.schemas import (
VerificationCreateRequest,
VerificationCreateResponse,
VerificationResendRequest,
VerificationVerifyRequest,
SessionCreateRequest,
SessionDeleteRequest,
SessionRefreshRequest,
SessionResponse,
UserByEmailResponse,
)
from v1.auth.service import AuthService
router = APIRouter(prefix="/auth", tags=["auth"])
@router.post("/verifications", response_model=VerificationCreateResponse, status_code=202)
async def create_verification(
payload: VerificationCreateRequest,
service: AuthService = Depends(get_auth_service),
) -> VerificationCreateResponse:
await enforce_rate_limit(
scope="signup_start",
identifier=payload.email,
limit=5,
window_seconds=60,
)
return await service.create_verification(payload)
@router.post("/verifications/resend", status_code=204)
async def resend_verification(
payload: VerificationResendRequest,
service: AuthService = Depends(get_auth_service),
) -> Response:
await enforce_rate_limit(
scope="signup_resend",
identifier=payload.email,
limit=5,
window_seconds=60,
)
await service.resend_verification(payload)
return Response(status_code=204)
@router.post("/verifications/verify", response_model=SessionResponse)
async def verify_verification(
payload: VerificationVerifyRequest,
service: AuthService = Depends(get_auth_service),
) -> SessionResponse:
await enforce_rate_limit(
scope="signup_verify",
identifier=payload.email,
limit=10,
window_seconds=600,
)
return await service.verify_verification(payload)
@router.post("/sessions", response_model=SessionResponse)
async def create_session(
payload: SessionCreateRequest,
service: AuthService = Depends(get_auth_service),
) -> SessionResponse:
await enforce_rate_limit(
scope="login",
identifier=payload.email,
limit=10,
window_seconds=60,
)
return await service.create_session(payload)
@router.post("/sessions/refresh", response_model=SessionResponse)
async def refresh_session(
payload: SessionRefreshRequest,
service: AuthService = Depends(get_auth_service),
) -> SessionResponse:
await enforce_rate_limit(
scope="refresh",
identifier=payload.refresh_token,
limit=10,
window_seconds=60,
)
return await service.refresh_session(payload)
@router.delete("/sessions", status_code=204)
async def delete_session(
payload: SessionDeleteRequest,
service: AuthService = Depends(get_auth_service),
) -> Response:
await enforce_rate_limit(
scope="logout",
identifier=payload.refresh_token,
limit=10,
window_seconds=60,
)
await service.delete_session(payload.refresh_token)
return Response(status_code=204)
@router.get("/users", response_model=UserByEmailResponse)
async def get_user_by_email(
email: str,
current_user: Annotated[CurrentUser, Depends(get_current_user)],
service: AuthService = Depends(get_auth_service),
) -> UserByEmailResponse:
if current_user.role != "service_role" and current_user.email != email:
raise HTTPException(status_code=403, detail="Forbidden")
return await service.get_user_by_email(email)
```
**Step 2: 验证类型检查**
Run: `cd backend && uv run basedpyright src/v1/auth/router.py`
Expected: No errors
**Step 3: Commit**
```bash
git add backend/src/v1/auth/router.py
git commit -m "refactor(auth): rename routes to RESTful style"
```
---
### Task 5: 重构 Auth Service
**Files:**
- Modify: `backend/src/v1/auth/service.py`
**Step 1: 更新 service 方法名和导入**
更新导入和方法名以匹配新的 schema:
```python
from v1.auth.schemas import (
VerificationCreateRequest,
VerificationCreateResponse,
VerificationResendRequest,
VerificationVerifyRequest,
SessionCreateRequest,
SessionDeleteRequest,
SessionRefreshRequest,
SessionResponse,
UserByEmailResponse,
)
```
将方法重命名:
- `signup_start``create_verification`
- `signup_resend``resend_verification`
- `signup_verify``verify_verification`
- `login``create_session`
- `refresh``refresh_session`
- `logout``delete_session`
`create_verification` 返回 `VerificationCreateResponse(email=payload.email)`
**Step 2: 验证类型检查**
Run: `cd backend && uv run basedpyright src/v1/auth/service.py`
Expected: No errors
**Step 3: Commit**
```bash
git add backend/src/v1/auth/service.py
git commit -m "refactor(auth): rename service methods for RESTful API"
```
---
### Task 6: 更新 Auth Gateway
**Files:**
- Modify: `backend/src/v1/auth/gateway.py`
**Step 1: 更新导入和方法调用**
更新导入使用新 schema 名称,方法调用更新为新的 service 方法名。
**Step 2: 验证类型检查**
Run: `cd backend && uv run basedpyright src/v1/auth/gateway.py`
Expected: No errors
**Step 3: Commit**
```bash
git add backend/src/v1/auth/gateway.py
git commit -m "refactor(auth): update gateway for new service methods"
```
---
### Task 7: 创建 Users Router
**Files:**
- Create: `backend/src/v1/users/router.py`
**Step 1: 创建 users router**
```python
from __future__ import annotations
from typing import Annotated
from fastapi import APIRouter, Depends, Path
from v1.users.dependencies import get_user_service
from v1.users.schemas import UserResponse, UserUpdateRequest
from v1.users.service import UserService
router = APIRouter(prefix="/users", tags=["users"])
@router.get("/me", response_model=UserResponse)
async def get_me(
service: Annotated[UserService, Depends(get_user_service)],
) -> UserResponse:
return await service.get_me()
@router.patch("/me", response_model=UserResponse)
async def update_me(
payload: UserUpdateRequest,
service: Annotated[UserService, Depends(get_user_service)],
) -> UserResponse:
return await service.update_me(payload)
@router.get("/{username}", response_model=UserResponse)
async def get_by_username(
username: Annotated[
str, Path(min_length=3, max_length=30, pattern="^[a-zA-Z0-9_]+$")
],
service: Annotated[UserService, Depends(get_user_service)],
) -> UserResponse:
return await service.get_by_username(username)
```
**Step 2: 验证类型检查**
Run: `cd backend && uv run basedpyright src/v1/users/router.py`
Expected: No errors
**Step 3: Commit**
```bash
git add backend/src/v1/users/router.py
git commit -m "feat(users): create users router"
```
---
### Task 8: 更新 Agent Chat Router
**Files:**
- Modify: `backend/src/v1/agent_chat/router.py`
**Step 1: 更新 URL 路径**
`/run` 改为根路径:
```python
@router.post("", response_model=AgentChatRunResponse)
async def run_agent_chat(
payload: AgentChatRunRequest,
service: Annotated[AgentChatService, Depends(get_agent_chat_service)],
) -> AgentChatRunResponse:
return await service.run(payload)
```
**Step 2: 验证类型检查**
Run: `cd backend && uv run basedpyright src/v1/agent_chat/router.py`
Expected: No errors
**Step 3: Commit**
```bash
git add backend/src/v1/agent_chat/router.py
git commit -m "refactor(agent-chat): change route to RESTful style"
```
---
### Task 9: 更新主路由注册
**Files:**
- Modify: `backend/src/v1/router.py`
**Step 1: 更新路由注册**
```python
from __future__ import annotations
from fastapi import APIRouter
from core.http.models import HealthResponse
from v1.agent_chat.router import router as agent_chat_router
from v1.auth.router import router as auth_router
from v1.infra.router import router as infra_router
from v1.users.router import router as users_router
router = APIRouter(prefix="/api/v1")
router.include_router(auth_router)
router.include_router(infra_router)
router.include_router(users_router)
router.include_router(agent_chat_router)
@router.get("/health", response_model=HealthResponse)
async def health() -> HealthResponse:
return HealthResponse(status="ok")
```
**Step 2: 验证类型检查**
Run: `cd backend && uv run basedpyright src/v1/router.py`
Expected: No errors
**Step 3: Commit**
```bash
git add backend/src/v1/router.py
git commit -m "refactor: register users router instead of profile"
```
---
## Phase 3: 后端测试更新
### Task 10: 更新 Auth 路由测试
**Files:**
- Modify: `backend/tests/integration/test_auth_routes.py`
**Step 1: 更新测试用例的 URL 和断言**
- `/signup/start``/auth/verifications`
- `/signup/resend``/auth/verifications/resend`
- `/signup/verify``/auth/verifications/verify`
- `/login``/auth/sessions`
- `/refresh``/auth/sessions/refresh`
- `/logout``/auth/sessions`
- `/users/by-email``/auth/users?email=xxx`
更新断言:
- `signup_start` 响应只包含 `email` 字段
- `signup_resend` 响应为 204,无 body
**Step 2: 运行测试验证**
Run: `cd backend && uv run pytest tests/integration/test_auth_routes.py -v`
Expected: All tests pass
**Step 3: Commit**
```bash
git add backend/tests/integration/test_auth_routes.py
git commit -m "test(auth): update integration tests for RESTful routes"
```
---
### Task 11: 创建 Users 路由测试
**Files:**
- Move: `backend/tests/integration/test_profile_routes.py``backend/tests/integration/test_users_routes.py`
**Step 1: 移动并更新测试文件**
- 更新 URL`/profile/me``/users/me``/profile/{username}``/users/{username}`
- 更新导入和断言
**Step 2: 运行测试验证**
Run: `cd backend && uv run pytest tests/integration/test_users_routes.py -v`
Expected: All tests pass
**Step 3: Commit**
```bash
git add backend/tests/integration/test_users_routes.py
git rm backend/tests/integration/test_profile_routes.py
git commit -m "test(users): rename profile tests to users"
```
---
### Task 12: 运行完整测试套件
**Step 1: 运行所有后端测试**
Run: `cd backend && uv run pytest -v`
Expected: All tests pass
**Step 2: 运行类型检查**
Run: `cd backend && uv run basedpyright src/`
Expected: No errors
**Step 3: 运行 lint**
Run: `cd backend && uv run ruff check src/`
Expected: No errors
---
## Phase 4: 前端适配
### Task 13: 更新 Auth API
**Files:**
- Modify: `apps/lib/features/auth/data/auth_api.dart`
**Step 1: 更新 URL 路径**
```dart
import 'package:social_app/core/api/api_client.dart';
import 'models/signup_request.dart';
import 'models/login_request.dart';
import 'models/auth_response.dart';
class AuthApi {
final ApiClient _client;
static const _prefix = '/api/v1/auth';
AuthApi(this._client);
Future<VerificationCreateResponse> createVerification(SignupStartRequest request) async {
final response = await _client.post(
'$_prefix/verifications',
data: request.toJson(),
);
return VerificationCreateResponse.fromJson(response.data);
}
Future<void> resendVerification(SignupResendRequest request) async {
await _client.post(
'$_prefix/verifications/resend',
data: request.toJson(),
);
}
Future<AuthResponse> verifyVerification(SignupVerifyRequest request) async {
final response = await _client.post(
'$_prefix/verifications/verify',
data: request.toJson(),
);
return AuthResponse.fromJson(response.data);
}
Future<AuthResponse> createSession(LoginRequest request) async {
final response = await _client.post(
'$_prefix/sessions',
data: request.toJson(),
);
return AuthResponse.fromJson(response.data);
}
Future<AuthResponse> refreshSession(RefreshRequest request) async {
final response = await _client.post(
'$_prefix/sessions/refresh',
data: request.toJson(),
);
return AuthResponse.fromJson(response.data);
}
Future<void> deleteSession(LogoutRequest request) async {
await _client.delete(
'$_prefix/sessions',
data: request.toJson(),
);
}
}
```
**Step 2: Commit**
```bash
git add apps/lib/features/auth/data/auth_api.dart
git commit -m "refactor(auth): update API routes to RESTful style"
```
---
### Task 14: 更新 Auth Response Models
**Files:**
- Modify: `apps/lib/features/auth/data/models/auth_response.dart`
**Step 1: 简化响应模型**
```dart
class AuthUser {
final String id;
final String email;
const AuthUser({required this.id, required this.email});
factory AuthUser.fromJson(Map<String, dynamic> json) {
return AuthUser(id: json['id'] as String, email: json['email'] as String);
}
}
class AuthResponse {
final String accessToken;
final String refreshToken;
final int expiresIn;
final String tokenType;
final AuthUser user;
const AuthResponse({
required this.accessToken,
required this.refreshToken,
required this.expiresIn,
required this.tokenType,
required this.user,
});
factory AuthResponse.fromJson(Map<String, dynamic> json) {
return AuthResponse(
accessToken: json['access_token'] as String,
refreshToken: json['refresh_token'] as String,
expiresIn: json['expires_in'] as int,
tokenType: json['token_type'] as String,
user: AuthUser.fromJson(json['user'] as Map<String, dynamic>),
);
}
}
class VerificationCreateResponse {
final String email;
const VerificationCreateResponse({required this.email});
factory VerificationCreateResponse.fromJson(Map<String, dynamic> json) {
return VerificationCreateResponse(email: json['email'] as String);
}
}
```
删除 `SignupStartResponse``SignupResendResponse`
**Step 2: Commit**
```bash
git add apps/lib/features/auth/data/models/auth_response.dart
git commit -m "refactor(auth): simplify response models"
```
---
### Task 15: 更新 Auth Repository
**Files:**
- Modify: `apps/lib/features/auth/data/auth_repository.dart`
- Modify: `apps/lib/features/auth/data/auth_repository_impl.dart`
**Step 1: 更新方法名和返回类型**
将方法名和返回类型更新为新的 API 方法。
**Step 2: Commit**
```bash
git add apps/lib/features/auth/data/auth_repository.dart
git add apps/lib/features/auth/data/auth_repository_impl.dart
git commit -m "refactor(auth): update repository for new API"
```
---
### Task 16: 更新 Register Cubit
**Files:**
- Modify: `apps/lib/features/auth/presentation/cubits/register_cubit.dart`
**Step 1: 更新 API 调用**
- `signupStart``createVerification`
- `signupResend``resendVerification`(现在返回 void
- 更新返回类型
**Step 2: Commit**
```bash
git add apps/lib/features/auth/presentation/cubits/register_cubit.dart
git commit -m "refactor(auth): update register cubit for new API"
```
---
### Task 17: 更新 Auth Cubit
**Files:**
- Modify: `apps/lib/features/auth/presentation/cubits/auth_cubit.dart`
**Step 1: 更新 API 调用**
- `login``createSession`
- `refresh``refreshSession`
- `logout``deleteSession`
**Step 2: Commit**
```bash
git add apps/lib/features/auth/presentation/cubits/auth_cubit.dart
git commit -m "refactor(auth): update auth cubit for new API"
```
---
### Task 18: 创建 Users API 和 Repository
**Files:**
- Create: `apps/lib/features/users/data/users_api.dart`
- Create: `apps/lib/features/users/data/users_repository.dart`
- Create: `apps/lib/features/users/data/users_repository_impl.dart`
- Create: `apps/lib/features/users/data/models/user_response.dart`
**Step 1: 创建 users_api.dart**
```dart
import 'package:social_app/core/api/api_client.dart';
import 'models/user_response.dart';
class UsersApi {
final ApiClient _client;
static const _prefix = '/api/v1/users';
UsersApi(this._client);
Future<UserResponse> getMe() async {
final response = await _client.get('$_prefix/me');
return UserResponse.fromJson(response.data);
}
Future<UserResponse> updateMe(UserUpdateRequest request) async {
final response = await _client.patch(
'$_prefix/me',
data: request.toJson(),
);
return UserResponse.fromJson(response.data);
}
Future<UserResponse> getByUsername(String username) async {
final response = await _client.get('$_prefix/$username');
return UserResponse.fromJson(response.data);
}
}
```
**Step 2: 创建 user_response.dart**
```dart
class UserResponse {
final String id;
final String username;
final String? avatarUrl;
final String? bio;
const UserResponse({
required this.id,
required this.username,
this.avatarUrl,
this.bio,
});
factory UserResponse.fromJson(Map<String, dynamic> json) {
return UserResponse(
id: json['id'] as String,
username: json['username'] as String,
avatarUrl: json['avatar_url'] as String?,
bio: json['bio'] as String?,
);
}
}
class UserUpdateRequest {
final String? username;
final String? avatarUrl;
final String? bio;
const UserUpdateRequest({this.username, this.avatarUrl, this.bio});
Map<String, dynamic> toJson() {
return {
if (username != null) 'username': username,
if (avatarUrl != null) 'avatar_url': avatarUrl,
if (bio != null) 'bio': bio,
};
}
}
```
**Step 3: Commit**
```bash
git add apps/lib/features/users/
git commit -m "feat(users): create users API and models"
```
---
### Task 19: 更新使用 Profile 的页面
**Files:**
- Modify: 所有引用 profile API 的 cubit 和 screen
**Step 1: 全局搜索 profile 引用**
Run: `cd apps && grep -r "profile" lib/ --include="*.dart"`
**Step 2: 更新为 users API**
更新所有引用 profile 的代码改为使用新的 users API。
**Step 3: Commit**
```bash
git add apps/lib/
git commit -m "refactor: migrate profile to users API"
```
---
### Task 20: 更新 ApiClient 添加 delete 和 patch 方法
**Files:**
- Modify: `apps/lib/core/api/api_client.dart`
**Step 1: 添加 delete 和 patch 方法**
```dart
Future<Response<T>> delete<T>(
String path, {
dynamic data,
Options? options,
}) async {
try {
return await _dio.delete<T>(path, data: data, options: options);
} on DioException catch (e) {
throw ApiException.fromDioError(e);
}
}
Future<Response<T>> patch<T>(
String path, {
dynamic data,
Options? options,
}) async {
try {
return await _dio.patch<T>(path, data: data, options: options);
} on DioException catch (e) {
throw ApiException.fromDioError(e);
}
}
```
**Step 2: Commit**
```bash
git add apps/lib/core/api/api_client.dart
git commit -m "feat(api): add delete and patch methods"
```
---
### Task 21: 更新前端测试
**Files:**
- Modify: `apps/test/features/auth/presentation/cubits/register_cubit_test.dart`
- 其他相关测试文件
**Step 1: 更新测试用例**
更新 mock 和断言以匹配新的 API 方法名和返回类型。
**Step 2: 运行测试**
Run: `cd apps && flutter test`
Expected: All tests pass
**Step 3: Commit**
```bash
git add apps/test/
git commit -m "test: update tests for RESTful API"
```
---
## Phase 5: 文档
### Task 22: 创建路由文档
**Files:**
- Create: `docs/runtime/runtime-route.md`
**Step 1: 创建路由文档**
```markdown
# 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` 字段显示给用户。
```
**Step 2: Commit**
```bash
git add docs/runtime/runtime-route.md
git commit -m "docs: add runtime route documentation"
```
---
### Task 23: 更新根目录 AGENTS.md
**Files:**
- Modify: `AGENTS.md`
**Step 1: 添加路由同步规则**
在文件末尾添加:
```markdown
## 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
```
**Step 2: Commit**
```bash
git add AGENTS.md
git commit -m "docs: add route sync rule to AGENTS.md"
```
---
## Phase 6: 验收
### Task 24: 端到端验证
**Step 1: 启动后端服务**
Run: `docker compose --env-file .env -f infra/docker/docker-compose.yml up -d`
**Step 2: 运行后端测试**
Run: `cd backend && uv run pytest -v`
Expected: All tests pass
**Step 3: 运行前端测试**
Run: `cd apps && flutter test`
Expected: All tests pass
**Step 4: 手动验证注册/登录流程**
使用 Flutter app 或 curl 测试:
1. POST /api/v1/auth/verifications - 注册
2. POST /api/v1/auth/verifications/verify - 验证
3. POST /api/v1/auth/sessions - 登录
4. GET /api/v1/users/me - 获取用户信息
5. DELETE /api/v1/auth/sessions - 登出
---
## Summary
| Phase | Tasks | Description |
|-------|-------|-------------|
| Phase 1 | 1-3 | 后端 Schema 重构 |
| Phase 2 | 4-9 | 后端路由重构 |
| Phase 3 | 10-12 | 后端测试更新 |
| Phase 4 | 13-21 | 前端适配 |
| Phase 5 | 22-23 | 文档 |
| Phase 6 | 24 | 验收 |