Files
agent/docs/superpowers/plans/2026-07-21-forest-ai-api-backend.md

231 lines
8.9 KiB
Markdown
Raw Permalink 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.
# 森林AI API And Backend Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 建立不兼容旧接口的 `/api/v1` camelCase 契约,并把后端拆分为职责清晰、以 identity 对外的业务模块。
**Architecture:** Gorm 模型继续集中在 `internal/models`HTTP request/response DTO 放在各业务包。`httpx` 负责通用错误、identity 参数和 CORSservice 负责所有权与业务规则handler 不直接构造存储路径或查询数据库。
**Tech Stack:** Go、Gin、Gorm、PostgreSQL、httptest。
## Global Constraints
- 外部 API 前缀为 `/api/v1`,字段为 camelCase对外 ID 为 UUIDv7 identity。
- 数据库模型和表名前缀规则保持 `Sa` / `sa_`
- session token 必须签名并过期handler 只从认证 middleware 读取用户。
- 文件路径必须由文件服务构造;标签必须限定在项目内。
- AI 请求必须限流并记录 provider、key 类型、action、status 和 error。
- 允许不兼容旧 API但不得自动删除生产数据。
---
### Task 1: 通用 v1 路由、错误和 identity 参数
**Files:**
- Create: `backend/internal/httpx/response.go`
- Create: `backend/internal/httpx/params.go`
- Create: `backend/internal/httpx/response_test.go`
- Modify: `backend/internal/httpx/router.go`
- Modify: `backend/internal/httpx/router_test.go`
- Modify: `backend/internal/config/config.go`
- Modify: `backend/etc/agent.dev.yaml`
**Interfaces:**
- Produces: `Error(c, status, code, message)`, `IdentityParam(c, name) (string, bool)` and `/api/v1` route group.
- [ ] **Step 1: Write failing response and routing tests**
```go
func TestErrorUsesStableEnvelope(t *testing.T) {
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
Error(ctx, http.StatusBadRequest, "invalid_request", "请求参数无效")
require.JSONEq(t, `{"error":{"code":"invalid_request","message":"请求参数无效"}}`, recorder.Body.String())
}
func TestStatusLivesUnderAPIV1(t *testing.T) {
router := NewRouter(config.Config{Env: "test"})
req := httptest.NewRequest(http.MethodGet, "/api/v1/status", nil)
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, req)
require.Equal(t, http.StatusOK, recorder.Code)
}
```
- [ ] **Step 2: Verify tests fail**
Run: `go test ./internal/httpx -run 'TestErrorUsesStableEnvelope|TestStatusLivesUnderAPIV1' -v`
Expected: FAIL because helpers and route do not exist.
- [ ] **Step 3: Implement response helpers and configured CORS**
Use:
```go
type ErrorBody struct { Code string `json:"code"`; Message string `json:"message"` }
type ErrorEnvelope struct { Error ErrorBody `json:"error"` }
func Error(c *gin.Context, status int, code, message string) {
c.AbortWithStatusJSON(status, ErrorEnvelope{Error: ErrorBody{Code: code, Message: message}})
}
```
Change the API group to `router.Group("/api/v1")`. Add `AllowedOrigins []string` to config and build the CORS map from config with safe development defaults.
- [ ] **Step 4: Verify the package**
Run: `go test ./internal/httpx -v`
Expected: PASS.
- [ ] **Step 5: Commit**
```powershell
git add backend/internal/httpx backend/internal/config backend/etc/agent.dev.yaml
git commit -m "refactor(api): introduce v1 response contract"
```
### Task 2: 项目 DTO、identity 查询与项目更新
**Files:**
- Create: `backend/internal/logic/projects/dto.go`
- Create: `backend/internal/logic/projects/ownership.go`
- Modify: `backend/internal/logic/projects/service.go`
- Modify: `backend/internal/logic/projects/handlers.go`
- Modify: `backend/internal/logic/projects/handlers_test.go`
- Modify: `backend/internal/logic/projects/service_test.go`
**Interfaces:**
- Produces: `ProjectDTO`, `CreateProjectRequest`, `UpdateProjectRequest`, `FindOwnedProject(userID, identity)`.
- [ ] **Step 1: Write failing contract tests**
Add tests that create a project, call `GET /api/v1/projects`, assert `id` equals its identity, assert no numeric `ID` field exists, and patch `name`, `identifier`, `icon`, `background`, `description` through `PATCH /api/v1/projects/:projectId`.
- [ ] **Step 2: Verify failures**
Run: `go test ./internal/logic/projects -run 'TestListProjectsUsesIdentityDTO|TestUpdateProject' -v`
Expected: FAIL on old response and missing PATCH route.
- [ ] **Step 3: Implement explicit DTO mapping**
```go
type ProjectDTO struct {
ID string `json:"id"`
Name string `json:"name"`
Identifier string `json:"identifier"`
Icon string `json:"icon"`
Background string `json:"background"`
Description string `json:"description"`
}
func projectDTO(project models.SaProject) ProjectDTO {
return ProjectDTO{ID: project.Identity, Name: project.Name, Identifier: project.Identifier, Icon: project.Icon, Background: project.Background, Description: project.Description}
}
```
Resolve projects with `owner_id = ? AND identity = ?`. Return 404 for missing owned projects and 409 for duplicate identifiers.
- [ ] **Step 4: Verify project contracts**
Run: `go test ./internal/logic/projects -v`
Expected: PASS.
- [ ] **Step 5: Commit**
```powershell
git add backend/internal/logic/projects
git commit -m "refactor(api): expose project identity DTOs"
```
### Task 3: 拆分工作区聚合查询
**Files:**
- Create: `backend/internal/logic/projects/workspace.go`
- Create: `backend/internal/logic/projects/workspace_test.go`
- Modify: `backend/internal/logic/projects/service.go`
- Modify: `backend/internal/logic/projects/dto.go`
**Interfaces:**
- Produces: `Workspace(userID uint, projectIdentity string) (WorkspaceDTO, error)`.
- [ ] **Step 1: Move existing workspace tests to identity-based expectations**
Assert that project, tasks, tags, notes/sources, sessions and cron plans all expose string identities and RFC3339 timestamps, and that tasks return the actual project-scoped tag.
- [ ] **Step 2: Verify tests fail against the old aggregate**
Run: `go test ./internal/logic/projects -run Workspace -v`
Expected: FAIL on numeric IDs and preformatted display times.
- [ ] **Step 3: Move workspace-only code and remove presentation formatting**
Move `workspaceCounts`, `workspaceChannels`, `workspaceInbox`, `workspaceTasks`, `workspaceAISessions`, `workspaceNotesSources` and `workspaceCronPlans` into `workspace.go`. DTO times must use `time.Time`/`*time.Time` JSON serialization; delete `displayTime`, `displayOptionalTime` and `displayDuration` from backend presentation logic.
- [ ] **Step 4: Verify and check file focus**
Run: `go test ./internal/logic/projects -v`
Expected: PASS; `service.go` contains project CRUD only and is under 300 lines.
- [ ] **Step 5: Commit**
```powershell
git add backend/internal/logic/projects
git commit -m "refactor(backend): isolate workspace aggregation"
```
### Task 4: 任务、标签、资料与计划任务职责迁移
**Files:**
- Create: `backend/internal/logic/tasks/dto.go`
- Create: `backend/internal/logic/tasks/handlers.go`
- Create: `backend/internal/logic/tasks/handlers_test.go`
- Create: `backend/internal/logic/files/handlers.go`
- Create: `backend/internal/logic/files/handlers_test.go`
- Create: `backend/internal/logic/projects/tag_handlers.go`
- Create: `backend/internal/logic/projects/cron_handlers.go`
- Modify: `backend/internal/logic/tasks/service.go`
- Modify: `backend/internal/logic/files/service.go`
- Modify: `backend/internal/logic/projects/handlers.go`
- Modify: `backend/cmd/api/main.go`
**Interfaces:**
- Produces: identity-based Task DTO and separate route registrars for tasks/files/tags/cron plans.
- [ ] **Step 1: Write failing registrar contract tests**
Cover create/update task, create/list tag, upload source and create cron plan. Assert camelCase responses, identity IDs, project ownership, tag scope and source paths that do not contain absolute storage roots.
- [ ] **Step 2: Verify tests fail**
Run: `go test ./internal/logic/tasks ./internal/logic/files ./internal/logic/projects -v`
Expected: FAIL because registrars and DTOs are missing.
- [ ] **Step 3: Move handlers without weakening rules**
`tasks.Service` must accept a DB transaction or repository dependency and retain explicit-share checks. `files.Handler` must call `files.Service.Save` before creating the source record. Tag and cron handlers may remain in `projects` but must not be in the core project handler file.
- [ ] **Step 4: Register every handler under `/api/v1`**
Construct and pass auth, projects, tasks, files, inbox, search and AI registrars in `cmd/api/main.go`. Add Chinese comments to ownership, sharing, Inbox confirmation and file-path boundaries.
- [ ] **Step 5: Verify backend**
Run: `go test ./...`
Expected: PASS.
- [ ] **Step 6: Commit**
```powershell
git add backend/internal/logic backend/cmd/api/main.go
git commit -m "refactor(backend): split project write responsibilities"
```