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

8.9 KiB
Raw Permalink Blame History

森林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/modelsHTTP 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

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:

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
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
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
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
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
git add backend/internal/logic backend/cmd/api/main.go
git commit -m "refactor(backend): split project write responsibilities"