diff --git a/docs/plans/2026-02-26-restful-api-refactor-design.md b/docs/plans/2026-02-26-restful-api-refactor-design.md new file mode 100644 index 0000000..ba73c09 --- /dev/null +++ b/docs/plans/2026-02-26-restful-api-refactor-design.md @@ -0,0 +1,190 @@ +# RESTful API 全面重构设计 + +**日期**: 2026-02-26 +**状态**: 待实现 + +## 目标 + +将后端 HTTP API 改造为完全符合 RESTful 规范,并同步更新前端适配代码。 + +## 设计原则 + +1. **资源导向 URL**:使用名词而非动词 +2. **HTTP 语义化**:状态通过状态码表达,响应体只包含必要数据 +3. **响应格式统一**:成功响应无冗余字段,错误响应使用 RFC 7807 +4. **文档同步**:路由变更必须同步更新文档 + +## URL 重构 + +### Auth 相关 + +| 功能 | 当前 URL | 新 URL | HTTP 方法 | +|------|----------|--------|-----------| +| 注册发起 | `/signup/start` | `/verifications` | POST | +| 重发验证码 | `/signup/resend` | `/verifications/resend` | POST | +| 验证码校验 | `/signup/verify` | `/verifications/verify` | POST | +| 登录 | `/login` | `/sessions` | POST | +| 刷新 Token | `/refresh` | `/sessions/refresh` | POST | +| 登出 | `/logout` | `/sessions` | DELETE | +| 按邮箱查用户 | `/users/by-email` | `/users?email=xxx` | GET | + +### Profile → Users 统一 + +| 功能 | 当前 URL | 新 URL | HTTP 方法 | +|------|----------|--------|-----------| +| 获取个人信息 | `/profile/me` | `/users/me` | GET | +| 更新个人信息 | `/profile/me` | `/users/me` | PATCH | +| 按用户名查用户 | `/profile/{username}` | `/users/{username}` | GET | + +### Agent Chat + +| 功能 | 当前 URL | 新 URL | HTTP 方法 | +|------|----------|--------|-----------| +| 运行对话 | `/agent-chat/run` | `/agent-chats` | POST | + +## 响应格式 + +### 成功响应 + +| 端点 | 状态码 | 响应体 | +|------|--------|--------| +| `POST /verifications` | 202 | `{email: string}` | +| `POST /verifications/resend` | 204 | 无 | +| `POST /verifications/verify` | 200 | `{access_token, refresh_token, expires_in, token_type, user}` | +| `POST /sessions` | 200 | 同上 | +| `POST /sessions/refresh` | 200 | 同上 | +| `DELETE /sessions` | 204 | 无 | +| `GET /users?email=xxx` | 200 | `{id, email, created_at, email_confirmed_at?}` | +| `GET /users/me` | 200 | `{id, username, avatar_url?, bio?}` | +| `PATCH /users/me` | 200 | 同上 | +| `GET /users/{username}` | 200 | 同上 | +| `POST /agent-chats` | 200 | `{session_id, output, events[]}` | + +### 错误响应 + +保持 RFC 7807 格式(已实现): + +```json +{ + "type": "about:blank", + "title": "Unauthorized", + "status": 401, + "detail": "Invalid verification code", + "instance": "/api/v1/verifications/verify" +} +``` + +前端解析优先级:`detail` → `message` → `error` → "请求失败" + +## Schema 定义 + +### 请求 Schema + +```python +# verifications +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}$") + +# sessions +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) + +# users +class UserUpdateRequest(BaseModel): + username: str | None = Field(default=None, min_length=3, max_length=30) + avatar_url: str | None = None + bio: str | None = Field(default=None, max_length=200) +``` + +### 响应 Schema + +```python +class VerificationCreateResponse(BaseModel): + email: EmailStr + +class SessionResponse(BaseModel): + access_token: str + refresh_token: str + expires_in: int + token_type: str + user: AuthUser + +class UserResponse(BaseModel): + id: str + username: str + avatar_url: str | None = None + bio: str | None = None + +class UserByEmailResponse(BaseModel): + id: str + email: EmailStr + created_at: str + email_confirmed_at: str | None = None +``` + +## 文件改动清单 + +### 后端 + +| 文件 | 改动 | +|------|------| +| `v1/auth/router.py` | URL 重命名,简化响应 | +| `v1/auth/schemas.py` | 重命名/简化模型 | +| `v1/auth/gateway.py` | 适配新 schema | +| `v1/auth/service.py` | 适配新 schema | +| `v1/profile/` → `v1/users/` | 目录重命名 | +| `v1/users/router.py` | URL 更新 | +| `v1/users/schemas.py` | 重命名模型 | +| `v1/agent_chat/router.py` | URL 更新 | +| `v1/router.py` | 更新路由注册 | +| 测试文件 | 同步更新 | + +### 前端 + +| 文件 | 改动 | +|------|------| +| `auth/data/auth_api.dart` | URL 更新 | +| `auth/data/models/auth_response.dart` | 简化 SignupStartResponse,删除 SignupResendResponse | +| `profile/data/profile_api.dart` | 改为 `users/data/users_api.dart` | +| `profile/data/models/profile_response.dart` | 重命名 | +| 相关 cubit/screen | 适配新 API | + +### 文档 + +| 文件 | 改动 | +|------|------| +| `docs/runtime/runtime-route.md` | 新增,记录所有路由 | +| `AGENTS.md` | 添加路由文档同步规则 | + +## 风险与缓解 + +| 风险 | 缓解措施 | +|------|----------| +| 前后端不同步导致功能失效 | 同时修改,一次性提交 | +| 测试覆盖不足 | 先更新测试,确保通过后再改实现 | +| 遗漏某个调用点 | 全局搜索旧 URL 字符串 | + +## 验收标准 + +- [ ] 所有后端测试通过 +- [ ] 所有前端测试通过 +- [ ] 手动验证注册/登录/登出流程 +- [ ] `runtime-route.md` 包含所有端点文档 +- [ ] `AGENTS.md` 包含路由同步规则 diff --git a/docs/plans/2026-02-26-restful-api-refactor-plan.md b/docs/plans/2026-02-26-restful-api-refactor-plan.md new file mode 100644 index 0000000..740d5ca --- /dev/null +++ b/docs/plans/2026-02-26-restful-api-refactor-plan.md @@ -0,0 +1,1513 @@ +# 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 createVerification(SignupStartRequest request) async { + final response = await _client.post( + '$_prefix/verifications', + data: request.toJson(), + ); + return VerificationCreateResponse.fromJson(response.data); + } + + Future resendVerification(SignupResendRequest request) async { + await _client.post( + '$_prefix/verifications/resend', + data: request.toJson(), + ); + } + + Future verifyVerification(SignupVerifyRequest request) async { + final response = await _client.post( + '$_prefix/verifications/verify', + data: request.toJson(), + ); + return AuthResponse.fromJson(response.data); + } + + Future createSession(LoginRequest request) async { + final response = await _client.post( + '$_prefix/sessions', + data: request.toJson(), + ); + return AuthResponse.fromJson(response.data); + } + + Future refreshSession(RefreshRequest request) async { + final response = await _client.post( + '$_prefix/sessions/refresh', + data: request.toJson(), + ); + return AuthResponse.fromJson(response.data); + } + + Future 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 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 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), + ); + } +} + +class VerificationCreateResponse { + final String email; + + const VerificationCreateResponse({required this.email}); + + factory VerificationCreateResponse.fromJson(Map 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 getMe() async { + final response = await _client.get('$_prefix/me'); + return UserResponse.fromJson(response.data); + } + + Future updateMe(UserUpdateRequest request) async { + final response = await _client.patch( + '$_prefix/me', + data: request.toJson(), + ); + return UserResponse.fromJson(response.data); + } + + Future 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 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 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> delete( + String path, { + dynamic data, + Options? options, +}) async { + try { + return await _dio.delete(path, data: data, options: options); + } on DioException catch (e) { + throw ApiException.fromDioError(e); + } +} + +Future> patch( + String path, { + dynamic data, + Options? options, +}) async { + try { + return await _dio.patch(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 | 验收 |