1514 lines
31 KiB
Markdown
1514 lines
31 KiB
Markdown
|
|
# 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 | 验收 |
|