Ver Fonte

refactor:重构Plane V2接口,对MCP提供支持

Jerry Liang há 2 meses atrás
pai
commit
cbd84a0a90
39 ficheiros alterados com 2231 adições e 3369 exclusões
  1. 1 1
      service/cmd/mcp/main.go
  2. 3 9
      service/cmd/mcp/tools/data/plane_api_handlers.go
  3. 3 1
      service/main.go
  4. 375 0
      service/repositories/api/plane_v2/client.go
  5. 0 410
      service/repositories/api/plane_v2/planeWebClient.go
  6. 0 367
      service/repositories/api/plane_v2/plane_asset.go
  7. 0 111
      service/repositories/api/plane_v2/plane_comment.go
  8. 0 279
      service/repositories/api/plane_v2/plane_cycle.go
  9. 0 210
      service/repositories/api/plane_v2/plane_intake.go
  10. 0 187
      service/repositories/api/plane_v2/plane_issue.go
  11. 0 114
      service/repositories/api/plane_v2/plane_label.go
  12. 0 91
      service/repositories/api/plane_v2/plane_link.go
  13. 0 243
      service/repositories/api/plane_v2/plane_member.go
  14. 0 297
      service/repositories/api/plane_v2/plane_module.go
  15. 0 258
      service/repositories/api/plane_v2/plane_page.go
  16. 0 151
      service/repositories/api/plane_v2/plane_project.go
  17. 0 75
      service/repositories/api/plane_v2/plane_relation.go
  18. 0 134
      service/repositories/api/plane_v2/plane_state.go
  19. 0 110
      service/repositories/api/plane_v2/plane_sticky.go
  20. 122 0
      service/repositories/api/plane_v2/plane_v2_api/asset.go
  21. 83 0
      service/repositories/api/plane_v2/plane_v2_api/comment.go
  22. 150 0
      service/repositories/api/plane_v2/plane_v2_api/cycle.go
  23. 104 0
      service/repositories/api/plane_v2/plane_v2_api/intake.go
  24. 144 0
      service/repositories/api/plane_v2/plane_v2_api/issue.go
  25. 81 0
      service/repositories/api/plane_v2/plane_v2_api/label.go
  26. 67 0
      service/repositories/api/plane_v2/plane_v2_api/link.go
  27. 143 0
      service/repositories/api/plane_v2/plane_v2_api/member.go
  28. 150 0
      service/repositories/api/plane_v2/plane_v2_api/module.go
  29. 119 0
      service/repositories/api/plane_v2/plane_v2_api/page.go
  30. 105 0
      service/repositories/api/plane_v2/plane_v2_api/project.go
  31. 53 0
      service/repositories/api/plane_v2/plane_v2_api/relation.go
  32. 96 0
      service/repositories/api/plane_v2/plane_v2_api/state.go
  33. 77 0
      service/repositories/api/plane_v2/plane_v2_api/sticky.go
  34. 78 0
      service/repositories/api/plane_v2/plane_v2_api/workspace.go
  35. 0 128
      service/repositories/api/plane_v2/plane_workspace.go
  36. 180 0
      service/repositories/api/plane_v2/service.go
  37. 69 0
      service/repositories/api/plane_v2/types.go
  38. 14 169
      service/repositories/repositories.go
  39. 14 24
      service/services/feedbackServices.go

+ 1 - 1
service/cmd/mcp/main.go

@@ -44,7 +44,7 @@ func main() {
 	}
 	cache.Init(cfg.App.RedisAddr)
 	minioUtil.InitClient(&cfg.App)
-	repositories.PlaneWebHttpInit(&cfg.PlaneWebApi)
+	repositories.InitPlane(&cfg.PlaneWebApi)
 
 	// 初始化 MCP FileStore
 	mcpFS := filestore.NewFileStore("download")

+ 3 - 9
service/cmd/mcp/tools/data/plane_api_handlers.go

@@ -243,9 +243,7 @@ func handlePlaneGetIntakeIssueDetail(ctx context.Context, req mcp.CallToolReques
 // === V2 Web API: Page Handlers ===
 
 func handlePlaneListPages(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
-	slug := repositories.PlaneWebGetWorkspaceSlug()
-	projectID := repositories.PlaneWebGetProjectId()
-	data, err := repositories.PlaneWebListPages(slug, projectID)
+	data, err := repositories.Plane.ListPages()
 	return planeRawResult(data, err, "列出页面")
 }
 
@@ -254,9 +252,7 @@ func handlePlaneGetPage(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
 	if err != nil {
 		return helper.ParamError("page_id"), nil
 	}
-	slug := repositories.PlaneWebGetWorkspaceSlug()
-	projectID := repositories.PlaneWebGetProjectId()
-	data, apiErr := repositories.PlaneWebGetPage(slug, projectID, id)
+	data, apiErr := repositories.Plane.GetPage(id)
 	return planeRawResult(data, apiErr, "获取页面详情")
 }
 
@@ -265,8 +261,6 @@ func handlePlaneGetPageDescription(ctx context.Context, req mcp.CallToolRequest)
 	if err != nil {
 		return helper.ParamError("page_id"), nil
 	}
-	slug := repositories.PlaneWebGetWorkspaceSlug()
-	projectID := repositories.PlaneWebGetProjectId()
-	data, apiErr := repositories.PlaneWebGetPageDescription(slug, projectID, id)
+	data, apiErr := repositories.Plane.GetPageDescription(id)
 	return planeRawResult(data, apiErr, "获取页面描述")
 }

+ 3 - 1
service/main.go

@@ -64,7 +64,9 @@ func main() {
 	repositories.PlaneHttpInit(&cfg.PlaneApi)
 
 	// 初始化PlaneWebApi
-	repositories.PlaneWebHttpInit(&cfg.PlaneWebApi)
+
+	// 初始化新架构 Plane(PlaneClient + PlaneService)
+	repositories.InitPlane(&cfg.PlaneWebApi)
 
 	// 初始化计划任务
 	scheduler.Init()

+ 375 - 0
service/repositories/api/plane_v2/client.go

@@ -0,0 +1,375 @@
+package plane_v2
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"fwh-toolbox-server/config"
+	"io"
+	"mime/multipart"
+	"net/http"
+	"net/http/cookiejar"
+	"net/url"
+	"strings"
+	"sync"
+	"time"
+
+	"go.uber.org/zap"
+)
+
+const clientTimeout = 60 * time.Second
+
+// Response HTTP 响应
+type Response struct {
+	StatusCode int
+	Status     string
+	Body       []byte
+}
+
+// PlaneClient Plane Web API 客户端(通讯层)
+type PlaneClient struct {
+	baseURL    string
+	email      string
+	password   string
+	csrfToken  string
+	httpClient *http.Client
+	loginMu    sync.Mutex
+}
+
+// 全局单例
+var globalClient *PlaneClient
+
+// InitPlaneClient 初始化全局 PlaneClient(启动时调用一次,失败直接 panic)
+func InitPlaneClient(cfg *config.PlaneWebApiConfig) {
+	if globalClient != nil {
+		zap.L().Warn("[PlaneClient] 重复调用 InitPlaneClient,已忽略")
+		return
+	}
+
+	client, err := newPlaneClient(cfg.Host, cfg.Email, cfg.Password)
+	if err != nil {
+		panic(fmt.Sprintf("[PlaneClient] 初始化失败: %v", err))
+	}
+	globalClient = client
+}
+
+// newPlaneClient 创建 Plane 客户端并执行登录
+func newPlaneClient(host, email, password string) (*PlaneClient, error) {
+	jar, err := cookiejar.New(nil)
+	if err != nil {
+		return nil, fmt.Errorf("创建CookieJar失败: %w", err)
+	}
+
+	c := &PlaneClient{
+		baseURL:  strings.TrimRight(host, "/"),
+		email:    email,
+		password: password,
+		httpClient: &http.Client{
+			Timeout: clientTimeout,
+			Jar:     jar,
+			CheckRedirect: func(req *http.Request, via []*http.Request) error {
+				return http.ErrUseLastResponse
+			},
+		},
+	}
+
+	if err := c.login(); err != nil {
+		return nil, fmt.Errorf("初始化登录失败: %w", err)
+	}
+
+	return c, nil
+}
+
+// IsSuccess 判断 HTTP 状态码是否成功
+func IsSuccess(statusCode int) bool {
+	return statusCode >= 200 && statusCode < 300
+}
+
+// ============ 公开 HTTP 方法 ============
+
+// Get 发送 GET 请求
+func (c *PlaneClient) Get(relativeURL string) (*Response, error) {
+	return c.ensureAuth(func() (*Response, error) {
+		return c.doRequest(http.MethodGet, relativeURL, nil)
+	})
+}
+
+// Post 发送 POST 请求(JSON)
+func (c *PlaneClient) Post(relativeURL string, body any) (*Response, error) {
+	return c.ensureAuth(func() (*Response, error) {
+		return c.doJSONRequest(http.MethodPost, relativeURL, body)
+	})
+}
+
+// Patch 发送 PATCH 请求(JSON)
+func (c *PlaneClient) Patch(relativeURL string, body any) (*Response, error) {
+	return c.ensureAuth(func() (*Response, error) {
+		return c.doJSONRequest(http.MethodPatch, relativeURL, body)
+	})
+}
+
+// Delete 发送 DELETE 请求
+func (c *PlaneClient) Delete(relativeURL string) (*Response, error) {
+	return c.ensureAuth(func() (*Response, error) {
+		return c.doRequest(http.MethodDelete, relativeURL, nil)
+	})
+}
+
+// PostForm 发送 multipart/form-data POST 请求
+func (c *PlaneClient) PostForm(relativeURL string, writeForm func(w *multipart.Writer) error) (*Response, error) {
+	return c.ensureAuth(func() (*Response, error) {
+		var buf bytes.Buffer
+		writer := multipart.NewWriter(&buf)
+		if err := writeForm(writer); err != nil {
+			return nil, fmt.Errorf("写入表单数据失败: %w", err)
+		}
+		writer.Close()
+
+		reqURL := c.baseURL + relativeURL
+		req, err := http.NewRequest(http.MethodPost, reqURL, &buf)
+		if err != nil {
+			return nil, fmt.Errorf("创建请求失败: %w", err)
+		}
+		req.Header.Set("Content-Type", writer.FormDataContentType())
+		req.Header.Set("Accept", "application/json")
+		req.Header.Set("Referer", c.baseURL)
+		req.Header.Set("X-CSRFToken", c.csrfToken)
+
+		return c.executeRequest(req)
+	})
+}
+
+// PostS3 上传文件到 S3(预签名 POST,不走认证)
+func (c *PlaneClient) PostS3(uploadURL string, fields map[string]string, fileData []byte, fileName string) error {
+	var buf bytes.Buffer
+	writer := multipart.NewWriter(&buf)
+
+	for k, v := range fields {
+		if err := writer.WriteField(k, v); err != nil {
+			return fmt.Errorf("写入 field %s 失败: %w", k, err)
+		}
+	}
+
+	part, err := writer.CreateFormFile("file", fileName)
+	if err != nil {
+		return fmt.Errorf("创建 form file 失败: %w", err)
+	}
+	if _, err := part.Write(fileData); err != nil {
+		return fmt.Errorf("写入文件数据失败: %w", err)
+	}
+	writer.Close()
+
+	req, err := http.NewRequest(http.MethodPost, uploadURL, &buf)
+	if err != nil {
+		return fmt.Errorf("创建上传请求失败: %w", err)
+	}
+	req.Header.Set("Content-Type", writer.FormDataContentType())
+
+	client := &http.Client{Timeout: 60 * time.Second}
+	resp, err := client.Do(req)
+	if err != nil {
+		return fmt.Errorf("文件上传失败: %w", err)
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
+		respBody, _ := io.ReadAll(resp.Body)
+		return fmt.Errorf("文件上传失败: %s, body=%s", resp.Status, string(respBody))
+	}
+
+	return nil
+}
+
+// GetRedirectLocation 发送 GET 请求并从 302 响应中提取 Location
+func (c *PlaneClient) GetRedirectLocation(relativeURL string) (string, error) {
+	reqURL := c.baseURL + relativeURL
+
+	req, err := http.NewRequest(http.MethodGet, reqURL, nil)
+	if err != nil {
+		return "", fmt.Errorf("创建请求失败: %w", err)
+	}
+	req.Header.Set("Accept", "application/json")
+	req.Header.Set("Referer", c.baseURL)
+
+	resp, err := c.httpClient.Do(req)
+	if err != nil {
+		return "", fmt.Errorf("请求失败: %w", err)
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode == http.StatusFound || resp.StatusCode == http.StatusMovedPermanently {
+		location := resp.Header.Get("Location")
+		if location != "" {
+			return location, nil
+		}
+	}
+
+	if IsSuccess(resp.StatusCode) {
+		return reqURL, nil
+	}
+
+	return "", fmt.Errorf("获取重定向URL失败: status=%d", resp.StatusCode)
+}
+
+// GetBaseURL 获取基础 URL
+func (c *PlaneClient) GetBaseURL() string {
+	return c.baseURL
+}
+
+// ============ 内部方法 ============
+
+// ensureAuth 确保已认证,401/403 时自动重新登录重试
+func (c *PlaneClient) ensureAuth(doRequest func() (*Response, error)) (*Response, error) {
+	resp, err := doRequest()
+	if err != nil {
+		return nil, err
+	}
+
+	if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
+		zap.L().Warn("[PlaneClient] 认证失败,尝试重新登录", zap.Int("status", resp.StatusCode))
+		if loginErr := c.login(); loginErr != nil {
+			return nil, fmt.Errorf("重新登录失败: %w", loginErr)
+		}
+		return doRequest()
+	}
+
+	return resp, nil
+}
+
+// login 执行登录流程
+func (c *PlaneClient) login() error {
+	c.loginMu.Lock()
+	defer c.loginMu.Unlock()
+
+	zap.L().Info("[PlaneClient] 开始登录")
+
+	// Step 1: 获取 CSRF Token
+	csrfURL := c.baseURL + "/auth/get-csrf-token/"
+	req, err := http.NewRequest(http.MethodGet, csrfURL, nil)
+	if err != nil {
+		return fmt.Errorf("创建CSRF请求失败: %w", err)
+	}
+	req.Header.Set("Referer", c.baseURL)
+
+	resp, err := c.httpClient.Do(req)
+	if err != nil {
+		return fmt.Errorf("获取CSRF Token失败: %w", err)
+	}
+	defer resp.Body.Close()
+
+	body, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return fmt.Errorf("读取CSRF响应失败: %w", err)
+	}
+
+	if resp.StatusCode != http.StatusOK {
+		return fmt.Errorf("获取CSRF Token失败, status=%d", resp.StatusCode)
+	}
+
+	var csrfResp struct {
+		CsrfToken string `json:"csrf_token"`
+	}
+	if err := json.Unmarshal(body, &csrfResp); err != nil {
+		return fmt.Errorf("解析CSRF Token失败: %w", err)
+	}
+	c.csrfToken = csrfResp.CsrfToken
+
+	// Step 2: 登录
+	signInURL := c.baseURL + "/auth/sign-in/"
+	formData := url.Values{}
+	formData.Set("email", c.email)
+	formData.Set("password", c.password)
+
+	req, err = http.NewRequest(http.MethodPost, signInURL, strings.NewReader(formData.Encode()))
+	if err != nil {
+		return fmt.Errorf("创建登录请求失败: %w", err)
+	}
+
+	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+	req.Header.Set("Referer", c.baseURL)
+	req.Header.Set("X-CSRFToken", c.csrfToken)
+	req.AddCookie(&http.Cookie{Name: "csrftoken", Value: c.csrfToken})
+
+	resp, err = c.httpClient.Do(req)
+	if err != nil {
+		return fmt.Errorf("登录请求失败: %w", err)
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode != http.StatusFound {
+		loginBody, _ := io.ReadAll(resp.Body)
+		return fmt.Errorf("登录失败, status=%d, body=%s", resp.StatusCode, string(loginBody))
+	}
+
+	// 验证 session cookie
+	parsedURL, _ := url.Parse(c.baseURL)
+	cookies := c.httpClient.Jar.Cookies(parsedURL)
+	for _, cookie := range cookies {
+		if cookie.Name == "session-id" {
+			zap.L().Info("[PlaneClient] 登录成功")
+			return nil
+		}
+	}
+
+	return fmt.Errorf("登录后未获取到session-id cookie")
+}
+
+// doRequest 发送不带 body 的请求
+func (c *PlaneClient) doRequest(method, relativeURL string, _ any) (*Response, error) {
+	reqURL := c.baseURL + relativeURL
+
+	req, err := http.NewRequest(method, reqURL, nil)
+	if err != nil {
+		return nil, fmt.Errorf("创建请求失败: %w", err)
+	}
+
+	req.Header.Set("Accept", "application/json")
+	req.Header.Set("Referer", c.baseURL)
+	if method != http.MethodGet {
+		req.Header.Set("X-CSRFToken", c.csrfToken)
+	}
+
+	return c.executeRequest(req)
+}
+
+// doJSONRequest 发送 JSON body 的请求
+func (c *PlaneClient) doJSONRequest(method, relativeURL string, body any) (*Response, error) {
+	reqURL := c.baseURL + relativeURL
+
+	jsonData, err := json.Marshal(body)
+	if err != nil {
+		return nil, fmt.Errorf("序列化请求体失败: %w", err)
+	}
+
+	req, err := http.NewRequest(method, reqURL, bytes.NewReader(jsonData))
+	if err != nil {
+		return nil, fmt.Errorf("创建请求失败: %w", err)
+	}
+
+	req.Header.Set("Content-Type", "application/json")
+	req.Header.Set("Accept", "application/json")
+	req.Header.Set("Referer", c.baseURL)
+	req.Header.Set("X-CSRFToken", c.csrfToken)
+
+	return c.executeRequest(req)
+}
+
+// executeRequest 执行请求并返回响应
+func (c *PlaneClient) executeRequest(req *http.Request) (*Response, error) {
+	resp, err := c.httpClient.Do(req)
+	if err != nil {
+		return nil, fmt.Errorf("请求失败: %w", err)
+	}
+	defer resp.Body.Close()
+
+	body, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return nil, fmt.Errorf("读取响应失败: %w", err)
+	}
+
+	return &Response{
+		StatusCode: resp.StatusCode,
+		Status:     resp.Status,
+		Body:       body,
+	}, nil
+}

+ 0 - 410
service/repositories/api/plane_v2/planeWebClient.go

@@ -1,410 +0,0 @@
-package plane_v2
-
-import (
-	"bytes"
-	"encoding/json"
-	"fmt"
-	"fwh-toolbox-server/config"
-	"io"
-	"mime/multipart"
-	"net/http"
-	"net/http/cookiejar"
-	"net/url"
-	"strings"
-	"sync"
-	"time"
-
-	"go.uber.org/zap"
-)
-
-// Plane v2 Web API客户端 - 使用Session+CSRF认证(Django SessionAuthentication)
-
-const (
-	webTimeout time.Duration = 60 * time.Second // 请求超时60s
-)
-
-var (
-	webBaseURL      string
-	webEmail        string
-	webPassword      string
-	webWorkspaceSlug string
-	webProjectId     string
-	webLabelBugId      string
-	webLabelFeatureId  string
-	webLabelFeedbackId string
-	webCsrfToken    string
-	webClient       *http.Client
-	webLoginMu      sync.Mutex
-)
-
-// planeWebResponse 封装Plane Web HTTP响应
-type planeWebResponse struct {
-	StatusCode int
-	Status     string
-	Body       []byte
-}
-
-// PlaneWebHttpInit 初始化Plane Web HTTP客户端配置
-func PlaneWebHttpInit(cfg *config.PlaneWebApiConfig) {
-	webBaseURL = strings.TrimRight(cfg.Host, "/")
-	webEmail = cfg.Email
-	webPassword = cfg.Password
-	webWorkspaceSlug = cfg.WorkspaceSlug
-	webProjectId = cfg.ProjectId
-	webLabelBugId = cfg.LabelBugId
-	webLabelFeatureId = cfg.LabelFeatureId
-	webLabelFeedbackId = cfg.LabelFeedbackId
-
-	jar, err := cookiejar.New(nil)
-	if err != nil {
-		zap.L().Fatal("[PlaneWeb] 创建CookieJar失败", zap.Error(err))
-	}
-
-	webClient = &http.Client{
-		Timeout: webTimeout,
-		Jar:     jar,
-		CheckRedirect: func(req *http.Request, via []*http.Request) error {
-			return http.ErrUseLastResponse
-		},
-	}
-
-	// 初始化时执行登录
-	if err := webLogin(); err != nil {
-		zap.L().Error("[PlaneWeb] 初始化登录失败", zap.Error(err))
-	}
-}
-
-// GetWorkspaceSlug 获取WorkspaceSlug
-func GetWorkspaceSlug() string {
-	return webWorkspaceSlug
-}
-
-// GetProjectId 获取ProjectId
-func GetProjectId() string {
-	return webProjectId
-}
-
-// GetLabelBugId 获取Bug标签ID
-func GetWebLabelBugId() string {
-	return webLabelBugId
-}
-
-// GetLabelFeatureId 获取Feature标签ID
-func GetWebLabelFeatureId() string {
-	return webLabelFeatureId
-}
-
-// GetLabelFeedbackId 获取Feedback标签ID
-func GetWebLabelFeedbackId() string {
-	return webLabelFeedbackId
-}
-
-// isWebSuccess 判断HTTP状态码是否表示成功(2xx或3xx重定向)
-func isWebSuccess(statusCode int) bool {
-	return statusCode >= 200 && statusCode < 300
-}
-
-// webLogin 执行登录流程:获取CSRF Token → 登录获取Session
-func webLogin() error {
-	webLoginMu.Lock()
-	defer webLoginMu.Unlock()
-
-	zap.L().Info("[PlaneWeb] 开始登录流程")
-
-	// Step 1: GET /auth/get-csrf-token/ 获取CSRF Token
-	csrfURL := webBaseURL + "/auth/get-csrf-token/"
-	req, err := http.NewRequest(http.MethodGet, csrfURL, nil)
-	if err != nil {
-		return fmt.Errorf("创建CSRF请求失败: %w", err)
-	}
-	req.Header.Set("Referer", webBaseURL)
-
-	resp, err := webClient.Do(req)
-	if err != nil {
-		return fmt.Errorf("获取CSRF Token请求失败: %w", err)
-	}
-	defer resp.Body.Close()
-
-	body, err := io.ReadAll(resp.Body)
-	if err != nil {
-		return fmt.Errorf("读取CSRF响应失败: %w", err)
-	}
-
-	if resp.StatusCode != http.StatusOK {
-		return fmt.Errorf("获取CSRF Token失败, status=%d, body=%s", resp.StatusCode, string(body))
-	}
-
-	// 解析CSRF Token
-	var csrfResp struct {
-		CsrfToken string `json:"csrf_token"`
-	}
-	if err := json.Unmarshal(body, &csrfResp); err != nil {
-		return fmt.Errorf("解析CSRF Token响应失败: %w", err)
-	}
-	webCsrfToken = csrfResp.CsrfToken
-
-	zap.L().Debug("[PlaneWeb] 获取CSRF Token成功", zap.String("token", webCsrfToken[:8]+"..."))
-
-	// Step 2: POST /auth/sign-in/ 登录
-	signInURL := webBaseURL + "/auth/sign-in/"
-	formData := url.Values{}
-	formData.Set("email", webEmail)
-	formData.Set("password", webPassword)
-
-	req, err = http.NewRequest(http.MethodPost, signInURL, strings.NewReader(formData.Encode()))
-	if err != nil {
-		return fmt.Errorf("创建登录请求失败: %w", err)
-	}
-
-	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
-	req.Header.Set("Referer", webBaseURL)
-	req.Header.Set("X-CSRFToken", webCsrfToken)
-	req.AddCookie(&http.Cookie{Name: "csrftoken", Value: webCsrfToken})
-
-	resp, err = webClient.Do(req)
-	if err != nil {
-		return fmt.Errorf("登录请求失败: %w", err)
-	}
-	defer resp.Body.Close()
-
-	// 登录成功返回302
-	if resp.StatusCode != http.StatusFound {
-		loginBody, _ := io.ReadAll(resp.Body)
-		return fmt.Errorf("登录失败, status=%d, body=%s", resp.StatusCode, string(loginBody))
-	}
-
-	// 验证session-id cookie是否已设置
-	parsedURL, _ := url.Parse(webBaseURL)
-	cookies := webClient.Jar.Cookies(parsedURL)
-	hasSession := false
-	for _, c := range cookies {
-		if c.Name == "session-id" {
-			hasSession = true
-			break
-		}
-	}
-
-	if !hasSession {
-		return fmt.Errorf("登录后未获取到session-id cookie")
-	}
-
-	zap.L().Info("[PlaneWeb] 登录成功")
-	return nil
-}
-
-// webEnsureAuth 确保已认证,如果收到401/403则重新登录并重试
-func webEnsureAuth(doRequest func() (*planeWebResponse, error)) (*planeWebResponse, error) {
-	resp, err := doRequest()
-	if err != nil {
-		return nil, err
-	}
-
-	// 如果是401或403,尝试重新登录后重试
-	if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
-		zap.L().Warn("[PlaneWeb] 收到认证错误,尝试重新登录", zap.Int("statusCode", resp.StatusCode))
-
-		if loginErr := webLogin(); loginErr != nil {
-			return nil, fmt.Errorf("重新登录失败: %w", loginErr)
-		}
-
-		// 重试请求
-		return doRequest()
-	}
-
-	return resp, nil
-}
-
-// planeWebGet 发送GET请求到Plane Web API
-func planeWebGet(relativeURL string) (*planeWebResponse, error) {
-	return webEnsureAuth(func() (*planeWebResponse, error) {
-		reqURL := webBaseURL + relativeURL
-
-		zap.L().Debug("[PlaneWeb] HttpGet", zap.String("url", reqURL))
-
-		req, err := http.NewRequest(http.MethodGet, reqURL, nil)
-		if err != nil {
-			return nil, fmt.Errorf("创建请求失败: %w", err)
-		}
-
-		req.Header.Set("Accept", "application/json")
-		req.Header.Set("Referer", webBaseURL)
-
-		resp, err := webClient.Do(req)
-		if err != nil {
-			return nil, fmt.Errorf("请求失败: %w", err)
-		}
-		defer resp.Body.Close()
-
-		body, err := io.ReadAll(resp.Body)
-		if err != nil {
-			return nil, fmt.Errorf("读取响应失败: %w", err)
-		}
-
-		return &planeWebResponse{
-			StatusCode: resp.StatusCode,
-			Status:     resp.Status,
-			Body:       body,
-		}, nil
-	})
-}
-
-// planeWebPost 发送POST请求到Plane Web API(JSON)
-func planeWebPost(relativeURL string, reqBody any) (*planeWebResponse, error) {
-	return webEnsureAuth(func() (*planeWebResponse, error) {
-		reqURL := webBaseURL + relativeURL
-
-		zap.L().Debug("[PlaneWeb] HttpPost", zap.String("url", reqURL))
-
-		jsonData, err := json.Marshal(reqBody)
-		if err != nil {
-			return nil, fmt.Errorf("序列化请求体失败: %w", err)
-		}
-
-		req, err := http.NewRequest(http.MethodPost, reqURL, bytes.NewReader(jsonData))
-		if err != nil {
-			return nil, fmt.Errorf("创建请求失败: %w", err)
-		}
-
-		req.Header.Set("Content-Type", "application/json")
-		req.Header.Set("Accept", "application/json")
-		req.Header.Set("Referer", webBaseURL)
-		req.Header.Set("X-CSRFToken", webCsrfToken)
-
-		resp, err := webClient.Do(req)
-		if err != nil {
-			return nil, fmt.Errorf("请求失败: %w", err)
-		}
-		defer resp.Body.Close()
-
-		body, err := io.ReadAll(resp.Body)
-		if err != nil {
-			return nil, fmt.Errorf("读取响应失败: %w", err)
-		}
-
-		return &planeWebResponse{
-			StatusCode: resp.StatusCode,
-			Status:     resp.Status,
-			Body:       body,
-		}, nil
-	})
-}
-
-// planeWebPatch 发送PATCH请求到Plane Web API(JSON)
-func planeWebPatch(relativeURL string, reqBody any) (*planeWebResponse, error) {
-	return webEnsureAuth(func() (*planeWebResponse, error) {
-		reqURL := webBaseURL + relativeURL
-
-		zap.L().Debug("[PlaneWeb] HttpPatch", zap.String("url", reqURL))
-
-		jsonData, err := json.Marshal(reqBody)
-		if err != nil {
-			return nil, fmt.Errorf("序列化请求体失败: %w", err)
-		}
-
-		req, err := http.NewRequest(http.MethodPatch, reqURL, bytes.NewReader(jsonData))
-		if err != nil {
-			return nil, fmt.Errorf("创建请求失败: %w", err)
-		}
-
-		req.Header.Set("Content-Type", "application/json")
-		req.Header.Set("Accept", "application/json")
-		req.Header.Set("Referer", webBaseURL)
-		req.Header.Set("X-CSRFToken", webCsrfToken)
-
-		resp, err := webClient.Do(req)
-		if err != nil {
-			return nil, fmt.Errorf("请求失败: %w", err)
-		}
-		defer resp.Body.Close()
-
-		body, err := io.ReadAll(resp.Body)
-		if err != nil {
-			return nil, fmt.Errorf("读取响应失败: %w", err)
-		}
-
-		return &planeWebResponse{
-			StatusCode: resp.StatusCode,
-			Status:     resp.Status,
-			Body:       body,
-		}, nil
-	})
-}
-
-// planeWebDelete 发送DELETE请求到Plane Web API
-func planeWebDelete(relativeURL string) (*planeWebResponse, error) {
-	return webEnsureAuth(func() (*planeWebResponse, error) {
-		reqURL := webBaseURL + relativeURL
-
-		zap.L().Debug("[PlaneWeb] HttpDelete", zap.String("url", reqURL))
-
-		req, err := http.NewRequest(http.MethodDelete, reqURL, nil)
-		if err != nil {
-			return nil, fmt.Errorf("创建请求失败: %w", err)
-		}
-
-		req.Header.Set("Accept", "application/json")
-		req.Header.Set("Referer", webBaseURL)
-		req.Header.Set("X-CSRFToken", webCsrfToken)
-
-		resp, err := webClient.Do(req)
-		if err != nil {
-			return nil, fmt.Errorf("请求失败: %w", err)
-		}
-		defer resp.Body.Close()
-
-		body, err := io.ReadAll(resp.Body)
-		if err != nil {
-			return nil, fmt.Errorf("读取响应失败: %w", err)
-		}
-
-		return &planeWebResponse{
-			StatusCode: resp.StatusCode,
-			Status:     resp.Status,
-			Body:       body,
-		}, nil
-	})
-}
-
-// planeWebPostForm 发送multipart/form-data POST请求到Plane Web API
-func planeWebPostForm(relativeURL string, writeForm func(w *multipart.Writer) error) (*planeWebResponse, error) {
-	return webEnsureAuth(func() (*planeWebResponse, error) {
-		reqURL := webBaseURL + relativeURL
-
-		zap.L().Debug("[PlaneWeb] HttpPostForm", zap.String("url", reqURL))
-
-		var buf bytes.Buffer
-		writer := multipart.NewWriter(&buf)
-
-		if err := writeForm(writer); err != nil {
-			return nil, fmt.Errorf("写入表单数据失败: %w", err)
-		}
-		writer.Close()
-
-		req, err := http.NewRequest(http.MethodPost, reqURL, &buf)
-		if err != nil {
-			return nil, fmt.Errorf("创建请求失败: %w", err)
-		}
-
-		req.Header.Set("Content-Type", writer.FormDataContentType())
-		req.Header.Set("Accept", "application/json")
-		req.Header.Set("Referer", webBaseURL)
-		req.Header.Set("X-CSRFToken", webCsrfToken)
-
-		resp, err := webClient.Do(req)
-		if err != nil {
-			return nil, fmt.Errorf("请求失败: %w", err)
-		}
-		defer resp.Body.Close()
-
-		body, err := io.ReadAll(resp.Body)
-		if err != nil {
-			return nil, fmt.Errorf("读取响应失败: %w", err)
-		}
-
-		return &planeWebResponse{
-			StatusCode: resp.StatusCode,
-			Status:     resp.Status,
-			Body:       body,
-		}, nil
-	})
-}

+ 0 - 367
service/repositories/api/plane_v2/plane_asset.go

@@ -1,367 +0,0 @@
-package plane_v2
-
-import (
-	"bytes"
-	"encoding/json"
-	"fmt"
-	"io"
-	"mime/multipart"
-	"net/http"
-	"strings"
-	"time"
-
-	"go.uber.org/zap"
-)
-
-// ==================== Asset v2 API ====================
-
-// Entity Type 常量
-const (
-	EntityIssueDescription      = "ISSUE_DESCRIPTION"
-	EntityIssueAttachment       = "ISSUE_ATTACHMENT"
-	EntityCommentDescription    = "COMMENT_DESCRIPTION"
-	EntityPageDescription       = "PAGE_DESCRIPTION"
-	EntityDraftIssueDescription = "DRAFT_ISSUE_DESCRIPTION"
-	EntityProjectCover          = "PROJECT_COVER"
-	EntityUserAvatar            = "USER_AVATAR"
-	EntityUserCover             = "USER_COVER"
-	EntityWorkspaceLogo         = "WORKSPACE_LOGO"
-)
-
-// WebAssetUploadResult 资源上传结果
-type WebAssetUploadResult struct {
-	UploadData struct {
-		URL    string            `json:"url"`
-		Fields map[string]string `json:"fields"`
-	} `json:"upload_data"`
-	AssetID  string `json:"asset_id"`
-	AssetURL string `json:"asset_url"`
-}
-
-// CreateProjectAsset 创建项目资源(获取预签名 URL)
-func CreateProjectAsset(slug, projectID, fileName, mimeType string, size int64, entityType string) (*WebAssetUploadResult, error) {
-	url := fmt.Sprintf("/api/assets/v2/workspaces/%s/projects/%s/", slug, projectID)
-	payload := map[string]interface{}{
-		"name":              fileName,
-		"type":              mimeType,
-		"size":              size,
-		"entity_type":       entityType,
-		"entity_identifier": "",
-	}
-
-	resp, err := planeWebPost(url, payload)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] CreateProjectAsset 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] CreateProjectAsset 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("CreateProjectAsset 失败: %s", resp.Status)
-	}
-
-	var result WebAssetUploadResult
-	if err := json.Unmarshal(resp.Body, &result); err != nil {
-		return nil, fmt.Errorf("解析资源上传响应失败: %w", err)
-	}
-
-	return &result, nil
-}
-
-// UploadToS3 上传文件到 S3(使用预签名 POST URL)
-func UploadToS3(uploadURL string, fields map[string]string, fileData []byte, fileName string) error {
-	// 替换容器内地址为宿主机地址
-	uploadURL = strings.ReplaceAll(uploadURL, "host.docker.internal", "localhost")
-
-	var buf bytes.Buffer
-	writer := multipart.NewWriter(&buf)
-
-	// 先写入所有 fields(必须在 file 之前)
-	for k, v := range fields {
-		if err := writer.WriteField(k, v); err != nil {
-			return fmt.Errorf("写入 field %s 失败: %w", k, err)
-		}
-	}
-
-	// 写入文件
-	part, err := writer.CreateFormFile("file", fileName)
-	if err != nil {
-		return fmt.Errorf("创建 form file 失败: %w", err)
-	}
-	if _, err := part.Write(fileData); err != nil {
-		return fmt.Errorf("写入文件数据失败: %w", err)
-	}
-	writer.Close()
-
-	req, err := http.NewRequest(http.MethodPost, uploadURL, &buf)
-	if err != nil {
-		return fmt.Errorf("创建上传请求失败: %w", err)
-	}
-	req.Header.Set("Content-Type", writer.FormDataContentType())
-
-	client := &http.Client{Timeout: 60 * time.Second}
-	resp, err := client.Do(req)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] S3 Upload 请求失败", zap.Error(err))
-		return fmt.Errorf("文件上传失败: %w", err)
-	}
-	defer resp.Body.Close()
-
-	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
-		respBody, _ := io.ReadAll(resp.Body)
-		zap.L().Error("[PlaneWeb] S3 Upload 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(respBody)),
-		)
-		return fmt.Errorf("文件上传失败: %s", resp.Status)
-	}
-
-	zap.L().Debug("[PlaneWeb] S3 Upload 成功")
-	return nil
-}
-
-// ConfirmProjectAssetUpload 确认项目资源上传完成
-func ConfirmProjectAssetUpload(slug, projectID, assetID string) error {
-	url := fmt.Sprintf("/api/assets/v2/workspaces/%s/projects/%s/%s/", slug, projectID, assetID)
-
-	// Plane 期望空对象 {} 作为请求体(不能传 nil,否则序列化为 null 导致 500)
-	resp, err := planeWebPatch(url, map[string]interface{}{})
-	if err != nil {
-		zap.L().Error("[PlaneWeb] ConfirmProjectAssetUpload 请求失败", zap.Error(err))
-		return err
-	}
-
-	if resp.StatusCode != 204 && !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] ConfirmProjectAssetUpload 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("ConfirmProjectAssetUpload 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// GetProjectAsset 下载项目资源(返回302重定向URL)
-func GetProjectAsset(slug, projectID, assetID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/assets/v2/workspaces/%s/projects/%s/%s/", slug, projectID, assetID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] GetProjectAsset 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	return resp.Body, nil
-}
-
-// GetProjectAssetURL 获取项目资源的预签名访问URL(从302 Location中提取)
-func GetProjectAssetURL(slug, projectID, assetID string) (string, error) {
-	reqURL := webBaseURL + fmt.Sprintf("/api/assets/v2/workspaces/%s/projects/%s/%s/", slug, projectID, assetID)
-
-	req, err := http.NewRequest(http.MethodGet, reqURL, nil)
-	if err != nil {
-		return "", fmt.Errorf("创建请求失败: %w", err)
-	}
-	req.Header.Set("Accept", "application/json")
-	req.Header.Set("Referer", webBaseURL)
-
-	resp, err := webClient.Do(req)
-	if err != nil {
-		return "", fmt.Errorf("请求失败: %w", err)
-	}
-	defer resp.Body.Close()
-
-	if resp.StatusCode == http.StatusFound || resp.StatusCode == http.StatusMovedPermanently {
-		location := resp.Header.Get("Location")
-		if location != "" {
-			return location, nil
-		}
-	}
-
-	if isWebSuccess(resp.StatusCode) {
-		return reqURL, nil
-	}
-
-	return "", fmt.Errorf("获取资源URL失败: status=%d", resp.StatusCode)
-}
-
-// DeleteProjectAsset 删除项目资源
-func DeleteProjectAsset(slug, projectID, assetID string) error {
-	url := fmt.Sprintf("/api/assets/v2/workspaces/%s/projects/%s/%s/", slug, projectID, assetID)
-
-	resp, err := planeWebDelete(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] DeleteProjectAsset 请求失败", zap.Error(err))
-		return err
-	}
-
-	if resp.StatusCode != 204 {
-		zap.L().Error("[PlaneWeb] DeleteProjectAsset 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("DeleteProjectAsset 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// BindAssetsToEntity 批量绑定资源到实体
-func BindAssetsToEntity(slug, projectID, entityID string, assetIDs []string) error {
-	url := fmt.Sprintf("/api/assets/v2/workspaces/%s/projects/%s/%s/bulk/", slug, projectID, entityID)
-	payload := map[string]interface{}{
-		"asset_ids": assetIDs,
-	}
-
-	resp, err := planeWebPost(url, payload)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] BindAssetsToEntity 请求失败", zap.Error(err))
-		return err
-	}
-
-	if resp.StatusCode != 204 && !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] BindAssetsToEntity 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("BindAssetsToEntity 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// CreateWorkspaceAsset 创建工作空间资源
-func CreateWorkspaceAsset(slug, fileName, mimeType string, size int64, entityType string) (*WebAssetUploadResult, error) {
-	url := fmt.Sprintf("/api/assets/v2/workspaces/%s/", slug)
-	payload := map[string]interface{}{
-		"name":              fileName,
-		"type":              mimeType,
-		"size":              size,
-		"entity_type":       entityType,
-		"entity_identifier": "",
-	}
-
-	resp, err := planeWebPost(url, payload)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] CreateWorkspaceAsset 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] CreateWorkspaceAsset 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("CreateWorkspaceAsset 失败: %s", resp.Status)
-	}
-
-	var result WebAssetUploadResult
-	if err := json.Unmarshal(resp.Body, &result); err != nil {
-		return nil, fmt.Errorf("解析资源上传响应失败: %w", err)
-	}
-
-	return &result, nil
-}
-
-// ConfirmWorkspaceAssetUpload 确认工作空间资源上传完成
-func ConfirmWorkspaceAssetUpload(slug, assetID string) error {
-	url := fmt.Sprintf("/api/assets/v2/workspaces/%s/%s/", slug, assetID)
-
-	resp, err := planeWebPatch(url, map[string]interface{}{})
-	if err != nil {
-		zap.L().Error("[PlaneWeb] ConfirmWorkspaceAssetUpload 请求失败", zap.Error(err))
-		return err
-	}
-
-	if resp.StatusCode != 204 && !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] ConfirmWorkspaceAssetUpload 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("ConfirmWorkspaceAssetUpload 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// CreateUserAsset 创建用户资源
-func CreateUserAsset(fileName, mimeType string, size int64, entityType string) (*WebAssetUploadResult, error) {
-	payload := map[string]interface{}{
-		"name":              fileName,
-		"type":              mimeType,
-		"size":              size,
-		"entity_type":       entityType,
-		"entity_identifier": "",
-	}
-
-	resp, err := planeWebPost("/api/assets/v2/user-assets/", payload)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] CreateUserAsset 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] CreateUserAsset 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("CreateUserAsset 失败: %s", resp.Status)
-	}
-
-	var result WebAssetUploadResult
-	if err := json.Unmarshal(resp.Body, &result); err != nil {
-		return nil, fmt.Errorf("解析资源上传响应失败: %w", err)
-	}
-
-	return &result, nil
-}
-
-// ConfirmUserAssetUpload 确认用户资源上传完成
-func ConfirmUserAssetUpload(assetID string) error {
-	url := fmt.Sprintf("/api/assets/v2/user-assets/%s/", assetID)
-
-	resp, err := planeWebPatch(url, map[string]interface{}{})
-	if err != nil {
-		zap.L().Error("[PlaneWeb] ConfirmUserAssetUpload 请求失败", zap.Error(err))
-		return err
-	}
-
-	if resp.StatusCode != 204 && !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] ConfirmUserAssetUpload 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("ConfirmUserAssetUpload 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// UploadProjectAsset 完整上传项目资源流程(组合方法)
-// 1. 创建资源获取预签名URL → 2. 上传到S3 → 3. 确认上传完成
-func UploadProjectAsset(slug, projectID, fileName, mimeType string, fileData []byte, entityType string) (*WebAssetUploadResult, error) {
-	fileSize := int64(len(fileData))
-
-	// Step 1: 创建资源
-	result, err := CreateProjectAsset(slug, projectID, fileName, mimeType, fileSize, entityType)
-	if err != nil {
-		return nil, fmt.Errorf("创建资源失败: %w", err)
-	}
-
-	zap.L().Debug("[PlaneWeb] Asset Create 成功",
-		zap.String("assetId", result.AssetID),
-		zap.String("uploadURL", result.UploadData.URL),
-	)
-
-	// Step 2: 上传到 S3
-	if err := UploadToS3(result.UploadData.URL, result.UploadData.Fields, fileData, fileName); err != nil {
-		return nil, fmt.Errorf("上传文件到S3失败: %w", err)
-	}
-
-	// Step 3: 确认上传完成
-	if err := ConfirmProjectAssetUpload(slug, projectID, result.AssetID); err != nil {
-		return nil, fmt.Errorf("确认上传失败: %w", err)
-	}
-
-	zap.L().Info("[PlaneWeb] Asset 上传完成",
-		zap.String("assetId", result.AssetID),
-		zap.String("fileName", fileName),
-		zap.String("assetUrl", result.AssetURL),
-	)
-
-	return result, nil
-}
-
-// UploadCurrentProjectAsset 在当前项目上传资源
-func UploadCurrentProjectAsset(fileName, mimeType string, fileData []byte, entityType string) (*WebAssetUploadResult, error) {
-	return UploadProjectAsset(webWorkspaceSlug, webProjectId, fileName, mimeType, fileData, entityType)
-}

+ 0 - 111
service/repositories/api/plane_v2/plane_comment.go

@@ -1,111 +0,0 @@
-package plane_v2
-
-import (
-	"fmt"
-
-	"go.uber.org/zap"
-)
-
-// ==================== Comment API ====================
-
-// ListComments 列出工作项评论
-func ListComments(slug, projectID, issueID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/comments/", slug, projectID, issueID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] ListComments 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] ListComments 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("ListComments 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// CreateComment 创建评论
-func CreateComment(slug, projectID, issueID, commentHTML string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/comments/", slug, projectID, issueID)
-	payload := map[string]interface{}{
-		"comment_html": commentHTML,
-	}
-
-	resp, err := planeWebPost(url, payload)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] CreateComment 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if resp.StatusCode != 201 {
-		zap.L().Error("[PlaneWeb] CreateComment 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("CreateComment 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// GetComment 获取评论详情
-func GetComment(slug, projectID, issueID, commentID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/comments/%s/", slug, projectID, issueID, commentID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] GetComment 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] GetComment 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("GetComment 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// UpdateComment 更新评论
-func UpdateComment(slug, projectID, issueID, commentID, commentHTML string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/comments/%s/", slug, projectID, issueID, commentID)
-	payload := map[string]interface{}{
-		"comment_html": commentHTML,
-	}
-
-	resp, err := planeWebPatch(url, payload)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] UpdateComment 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] UpdateComment 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("UpdateComment 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// DeleteComment 删除评论
-func DeleteComment(slug, projectID, issueID, commentID string) error {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/comments/%s/", slug, projectID, issueID, commentID)
-
-	resp, err := planeWebDelete(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] DeleteComment 请求失败", zap.Error(err))
-		return err
-	}
-
-	if resp.StatusCode != 204 {
-		zap.L().Error("[PlaneWeb] DeleteComment 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("DeleteComment 失败: %s", resp.Status)
-	}
-
-	return nil
-}

+ 0 - 279
service/repositories/api/plane_v2/plane_cycle.go

@@ -1,279 +0,0 @@
-package plane_v2
-
-import (
-	"fmt"
-
-	"go.uber.org/zap"
-)
-
-// ==================== Cycle API ====================
-
-// ListCycles 列出迭代
-func ListCycles(slug, projectID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/", slug, projectID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] ListCycles 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] ListCycles 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("ListCycles 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// CreateCycle 创建迭代
-func CreateCycle(slug, projectID string, data map[string]interface{}) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/", slug, projectID)
-
-	resp, err := planeWebPost(url, data)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] CreateCycle 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if resp.StatusCode != 201 {
-		zap.L().Error("[PlaneWeb] CreateCycle 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("CreateCycle 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// GetCycle 获取迭代详情
-func GetCycle(slug, projectID, cycleID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/", slug, projectID, cycleID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] GetCycle 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] GetCycle 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("GetCycle 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// UpdateCycle 更新迭代
-func UpdateCycle(slug, projectID, cycleID string, data map[string]interface{}) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/", slug, projectID, cycleID)
-
-	resp, err := planeWebPatch(url, data)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] UpdateCycle 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] UpdateCycle 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("UpdateCycle 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// DeleteCycle 删除迭代
-func DeleteCycle(slug, projectID, cycleID string) error {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/", slug, projectID, cycleID)
-
-	resp, err := planeWebDelete(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] DeleteCycle 请求失败", zap.Error(err))
-		return err
-	}
-
-	if resp.StatusCode != 204 {
-		zap.L().Error("[PlaneWeb] DeleteCycle 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("DeleteCycle 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// ListCycleIssues 列出迭代中的工作项
-func ListCycleIssues(slug, projectID, cycleID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/cycle-issues/", slug, projectID, cycleID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] ListCycleIssues 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] ListCycleIssues 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("ListCycleIssues 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// AddIssuesToCycle 添加工作项到迭代
-func AddIssuesToCycle(slug, projectID, cycleID string, issueIDs []string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/cycle-issues/", slug, projectID, cycleID)
-	payload := map[string]interface{}{
-		"issues": issueIDs,
-	}
-
-	resp, err := planeWebPost(url, payload)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] AddIssuesToCycle 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] AddIssuesToCycle 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("AddIssuesToCycle 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// RemoveIssueFromCycle 从迭代移除工作项
-func RemoveIssueFromCycle(slug, projectID, cycleID, issueID string) error {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/cycle-issues/%s/", slug, projectID, cycleID, issueID)
-
-	resp, err := planeWebDelete(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] RemoveIssueFromCycle 请求失败", zap.Error(err))
-		return err
-	}
-
-	if resp.StatusCode != 204 {
-		zap.L().Error("[PlaneWeb] RemoveIssueFromCycle 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("RemoveIssueFromCycle 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// TransferCycleIssues 转移未完成工作项到另一迭代
-func TransferCycleIssues(slug, projectID, cycleID, newCycleID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/transfer-issues/", slug, projectID, cycleID)
-	payload := map[string]interface{}{
-		"new_cycle_id": newCycleID,
-	}
-
-	resp, err := planeWebPost(url, payload)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] TransferCycleIssues 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] TransferCycleIssues 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("TransferCycleIssues 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// ArchiveCycle 归档迭代
-func ArchiveCycle(slug, projectID, cycleID string) error {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/archive/", slug, projectID, cycleID)
-
-	resp, err := planeWebPost(url, map[string]interface{}{})
-	if err != nil {
-		zap.L().Error("[PlaneWeb] ArchiveCycle 请求失败", zap.Error(err))
-		return err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] ArchiveCycle 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("ArchiveCycle 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// ListArchivedCycles 列出已归档迭代
-func ListArchivedCycles(slug, projectID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/archived-cycles/", slug, projectID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] ListArchivedCycles 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] ListArchivedCycles 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("ListArchivedCycles 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// UnarchiveCycle 取消归档迭代
-func UnarchiveCycle(slug, projectID, cycleID string) error {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/archived-cycles/%s/", slug, projectID, cycleID)
-
-	resp, err := planeWebDelete(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] UnarchiveCycle 请求失败", zap.Error(err))
-		return err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] UnarchiveCycle 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("UnarchiveCycle 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// GetCycleProgress 获取迭代进度
-func GetCycleProgress(slug, projectID, cycleID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/progress/", slug, projectID, cycleID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] GetCycleProgress 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] GetCycleProgress 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("GetCycleProgress 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// GetCycleAnalytics 获取迭代分析
-func GetCycleAnalytics(slug, projectID, cycleID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/analytics/", slug, projectID, cycleID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] GetCycleAnalytics 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] GetCycleAnalytics 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("GetCycleAnalytics 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}

+ 0 - 210
service/repositories/api/plane_v2/plane_intake.go

@@ -1,210 +0,0 @@
-package plane_v2
-
-import (
-	"encoding/json"
-	"fmt"
-
-	"go.uber.org/zap"
-)
-
-// ==================== Intake (收件箱) API ====================
-
-// WebIntakeIssueRequest 创建Intake工作项的请求结构
-type WebIntakeIssueRequest struct {
-	Name            string   `json:"name"`
-	DescriptionHTML string   `json:"description_html,omitempty"`
-	Priority        string   `json:"priority,omitempty"`
-	StateID         string   `json:"state_id,omitempty"`
-	AssigneeIDs     []string `json:"assignee_ids,omitempty"`
-	LabelIDs        []string `json:"label_ids,omitempty"`
-	StartDate       string   `json:"start_date,omitempty"`
-	TargetDate      *string  `json:"target_date"`
-	ParentID        string   `json:"parent_id,omitempty"`
-}
-
-// WebIntakeIssueResponse Intake创建响应
-type WebIntakeIssueResponse struct {
-	Issue struct {
-		ID              string `json:"id"`
-		Name            string `json:"name"`
-		DescriptionHTML string `json:"description_html"`
-		Priority        string `json:"priority"`
-	} `json:"issue"`
-	Status    int    `json:"status"`
-	CreatedAt string `json:"created_at"`
-}
-
-// ListIntakeIssues 列出收件箱工作项
-// status: 逗号分隔的状态值,如 "-2,-1" (可为空表示不过滤)
-func ListIntakeIssues(slug, projectID, status string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/intake-issues/", slug, projectID)
-	if status != "" {
-		url += "?status=" + status
-	}
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] ListIntakeIssues 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] ListIntakeIssues 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("ListIntakeIssues 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// CreateIntakeIssue 创建收件箱工作项
-func CreateIntakeIssue(slug, projectID string, issue WebIntakeIssueRequest) (*WebIntakeIssueResponse, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/intake-issues/", slug, projectID)
-	payload := map[string]interface{}{
-		"source": "IN_APP",
-		"issue":  issue,
-	}
-
-	resp, err := planeWebPost(url, payload)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] CreateIntakeIssue 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] CreateIntakeIssue 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("CreateIntakeIssue 失败: %s", resp.Status)
-	}
-
-	var result WebIntakeIssueResponse
-	if err := json.Unmarshal(resp.Body, &result); err != nil {
-		zap.L().Error("[PlaneWeb] CreateIntakeIssue 响应解析失败", zap.Error(err))
-		return nil, fmt.Errorf("响应解析失败: %w", err)
-	}
-
-	zap.L().Info("[PlaneWeb] CreateIntakeIssue 成功", zap.String("issueId", result.Issue.ID))
-	return &result, nil
-}
-
-// CreateIntakeIssueRaw 创建收件箱工作项(原始map方式)
-func CreateIntakeIssueRaw(slug, projectID string, issueData map[string]interface{}) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/intake-issues/", slug, projectID)
-	payload := map[string]interface{}{
-		"issue": issueData,
-	}
-
-	resp, err := planeWebPost(url, payload)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] CreateIntakeIssueRaw 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] CreateIntakeIssueRaw 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("CreateIntakeIssueRaw 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// GetIntakeIssue 获取收件箱工作项详情
-func GetIntakeIssue(slug, projectID, issueID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/intake-issues/%s/", slug, projectID, issueID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] GetIntakeIssue 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] GetIntakeIssue 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("GetIntakeIssue 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// UpdateIntakeIssue 更新收件箱工作项(修改状态或内容)
-func UpdateIntakeIssue(slug, projectID, issueID string, data map[string]interface{}) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/intake-issues/%s/", slug, projectID, issueID)
-
-	resp, err := planeWebPatch(url, data)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] UpdateIntakeIssue 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] UpdateIntakeIssue 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("UpdateIntakeIssue 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// AcceptIntakeIssue 接受收件箱工作项
-func AcceptIntakeIssue(slug, projectID, issueID string) ([]byte, error) {
-	return UpdateIntakeIssue(slug, projectID, issueID, map[string]interface{}{
-		"status": 1,
-	})
-}
-
-// RejectIntakeIssue 拒绝收件箱工作项
-func RejectIntakeIssue(slug, projectID, issueID string) ([]byte, error) {
-	return UpdateIntakeIssue(slug, projectID, issueID, map[string]interface{}{
-		"status": -1,
-	})
-}
-
-// SnoozeIntakeIssue 暂缓收件箱工作项
-func SnoozeIntakeIssue(slug, projectID, issueID, snoozedTill string) ([]byte, error) {
-	return UpdateIntakeIssue(slug, projectID, issueID, map[string]interface{}{
-		"status":       0,
-		"snoozed_till": snoozedTill,
-	})
-}
-
-// DuplicateIntakeIssue 标记收件箱工作项为重复
-func DuplicateIntakeIssue(slug, projectID, issueID, duplicateTo string) ([]byte, error) {
-	return UpdateIntakeIssue(slug, projectID, issueID, map[string]interface{}{
-		"status":       2,
-		"duplicate_to": duplicateTo,
-	})
-}
-
-// DeleteIntakeIssue 删除收件箱工作项
-func DeleteIntakeIssue(slug, projectID, issueID string) error {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/intake-issues/%s/", slug, projectID, issueID)
-
-	resp, err := planeWebDelete(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] DeleteIntakeIssue 请求失败", zap.Error(err))
-		return err
-	}
-
-	if resp.StatusCode != 204 {
-		zap.L().Error("[PlaneWeb] DeleteIntakeIssue 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("DeleteIntakeIssue 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// ListCurrentIntakeIssues 列出当前项目的收件箱工作项
-func ListCurrentIntakeIssues(status string) ([]byte, error) {
-	return ListIntakeIssues(webWorkspaceSlug, webProjectId, status)
-}
-
-// CreateCurrentIntakeIssue 在当前项目创建收件箱工作项
-func CreateCurrentIntakeIssue(issue WebIntakeIssueRequest) (*WebIntakeIssueResponse, error) {
-	return CreateIntakeIssue(webWorkspaceSlug, webProjectId, issue)
-}

+ 0 - 187
service/repositories/api/plane_v2/plane_issue.go

@@ -1,187 +0,0 @@
-package plane_v2
-
-import (
-	"fmt"
-
-	"go.uber.org/zap"
-)
-
-// ==================== Issue (Work Item) API ====================
-
-// ListIssues 列出工作项
-func ListIssues(slug, projectID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/", slug, projectID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] ListIssues 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] ListIssues 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("ListIssues 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// CreateIssue 创建工作项
-func CreateIssue(slug, projectID string, data map[string]interface{}) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/", slug, projectID)
-
-	resp, err := planeWebPost(url, data)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] CreateIssue 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if resp.StatusCode != 201 {
-		zap.L().Error("[PlaneWeb] CreateIssue 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("CreateIssue 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// GetIssue 获取工作项详情
-func GetIssue(slug, projectID, issueID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/", slug, projectID, issueID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] GetIssue 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] GetIssue 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("GetIssue 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// UpdateIssue 更新工作项
-func UpdateIssue(slug, projectID, issueID string, data map[string]interface{}) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/", slug, projectID, issueID)
-
-	resp, err := planeWebPatch(url, data)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] UpdateIssue 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] UpdateIssue 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("UpdateIssue 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// DeleteIssue 删除工作项
-func DeleteIssue(slug, projectID, issueID string) error {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/", slug, projectID, issueID)
-
-	resp, err := planeWebDelete(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] DeleteIssue 请求失败", zap.Error(err))
-		return err
-	}
-
-	if resp.StatusCode != 204 {
-		zap.L().Error("[PlaneWeb] DeleteIssue 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("DeleteIssue 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// GetSubIssues 获取子工作项
-func GetSubIssues(slug, projectID, issueID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/sub-issues/", slug, projectID, issueID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] GetSubIssues 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] GetSubIssues 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("GetSubIssues 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// ArchiveIssue 归档工作项
-func ArchiveIssue(slug, projectID, issueID string) error {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/archive/", slug, projectID, issueID)
-
-	resp, err := planeWebPost(url, map[string]interface{}{})
-	if err != nil {
-		zap.L().Error("[PlaneWeb] ArchiveIssue 请求失败", zap.Error(err))
-		return err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] ArchiveIssue 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("ArchiveIssue 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// UnarchiveIssue 取消归档工作项
-func UnarchiveIssue(slug, projectID, issueID string) error {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/archive/", slug, projectID, issueID)
-
-	resp, err := planeWebDelete(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] UnarchiveIssue 请求失败", zap.Error(err))
-		return err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] UnarchiveIssue 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("UnarchiveIssue 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// GetIssueActivity 获取工作项活动历史
-func GetIssueActivity(slug, projectID, issueID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/history/", slug, projectID, issueID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] GetIssueActivity 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] GetIssueActivity 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("GetIssueActivity 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// ListCurrentIssues 列出当前项目下的工作项
-func ListCurrentIssues() ([]byte, error) {
-	return ListIssues(webWorkspaceSlug, webProjectId)
-}
-
-// CreateCurrentIssue 在当前项目下创建工作项
-func CreateCurrentIssue(data map[string]interface{}) ([]byte, error) {
-	return CreateIssue(webWorkspaceSlug, webProjectId, data)
-}

+ 0 - 114
service/repositories/api/plane_v2/plane_label.go

@@ -1,114 +0,0 @@
-package plane_v2
-
-import (
-	"fmt"
-
-	"go.uber.org/zap"
-)
-
-// ==================== Label API ====================
-
-// ListLabels 列出标签
-func ListLabels(slug, projectID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issue-labels/", slug, projectID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] ListLabels 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] ListLabels 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("ListLabels 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// CreateLabel 创建标签
-func CreateLabel(slug, projectID, name, color string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issue-labels/", slug, projectID)
-	payload := map[string]interface{}{
-		"name":  name,
-		"color": color,
-	}
-
-	resp, err := planeWebPost(url, payload)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] CreateLabel 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if resp.StatusCode != 201 {
-		zap.L().Error("[PlaneWeb] CreateLabel 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("CreateLabel 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// GetLabel 获取标签详情
-func GetLabel(slug, projectID, labelID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issue-labels/%s/", slug, projectID, labelID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] GetLabel 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] GetLabel 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("GetLabel 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// UpdateLabel 更新标签
-func UpdateLabel(slug, projectID, labelID string, data map[string]interface{}) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issue-labels/%s/", slug, projectID, labelID)
-
-	resp, err := planeWebPatch(url, data)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] UpdateLabel 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] UpdateLabel 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("UpdateLabel 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// DeleteLabel 删除标签
-func DeleteLabel(slug, projectID, labelID string) error {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issue-labels/%s/", slug, projectID, labelID)
-
-	resp, err := planeWebDelete(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] DeleteLabel 请求失败", zap.Error(err))
-		return err
-	}
-
-	if resp.StatusCode != 204 {
-		zap.L().Error("[PlaneWeb] DeleteLabel 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("DeleteLabel 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// ListCurrentLabels 列出当前项目下的标签
-func ListCurrentLabels() ([]byte, error) {
-	return ListLabels(webWorkspaceSlug, webProjectId)
-}

+ 0 - 91
service/repositories/api/plane_v2/plane_link.go

@@ -1,91 +0,0 @@
-package plane_v2
-
-import (
-	"fmt"
-
-	"go.uber.org/zap"
-)
-
-// ==================== Issue Link API ====================
-
-// ListIssueLinks 列出工作项链接
-func ListIssueLinks(slug, projectID, issueID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/issue-links/", slug, projectID, issueID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] ListIssueLinks 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] ListIssueLinks 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("ListIssueLinks 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// CreateIssueLink 创建工作项链接
-func CreateIssueLink(slug, projectID, issueID, linkURL, title string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/issue-links/", slug, projectID, issueID)
-	payload := map[string]interface{}{
-		"url":   linkURL,
-		"title": title,
-	}
-
-	resp, err := planeWebPost(url, payload)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] CreateIssueLink 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if resp.StatusCode != 201 {
-		zap.L().Error("[PlaneWeb] CreateIssueLink 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("CreateIssueLink 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// UpdateIssueLink 更新工作项链接
-func UpdateIssueLink(slug, projectID, issueID, linkID string, data map[string]interface{}) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/issue-links/%s/", slug, projectID, issueID, linkID)
-
-	resp, err := planeWebPatch(url, data)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] UpdateIssueLink 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] UpdateIssueLink 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("UpdateIssueLink 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// DeleteIssueLink 删除工作项链接
-func DeleteIssueLink(slug, projectID, issueID, linkID string) error {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/issue-links/%s/", slug, projectID, issueID, linkID)
-
-	resp, err := planeWebDelete(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] DeleteIssueLink 请求失败", zap.Error(err))
-		return err
-	}
-
-	if resp.StatusCode != 204 {
-		zap.L().Error("[PlaneWeb] DeleteIssueLink 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("DeleteIssueLink 失败: %s", resp.Status)
-	}
-
-	return nil
-}

+ 0 - 243
service/repositories/api/plane_v2/plane_member.go

@@ -1,243 +0,0 @@
-package plane_v2
-
-import (
-	"fmt"
-
-	"go.uber.org/zap"
-)
-
-// ==================== Member API ====================
-
-// Member 角色常量
-const (
-	RoleAdmin  = 20
-	RoleMember = 15
-	RoleGuest  = 5
-)
-
-// ListWorkspaceMembers 列出工作空间成员
-func ListWorkspaceMembers(slug string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/members/", slug)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] ListWorkspaceMembers 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] ListWorkspaceMembers 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("ListWorkspaceMembers 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// GetWorkspaceMember 获取工作空间成员详情
-func GetWorkspaceMember(slug, memberID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/members/%s/", slug, memberID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] GetWorkspaceMember 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] GetWorkspaceMember 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("GetWorkspaceMember 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// UpdateWorkspaceMemberRole 更新工作空间成员角色
-func UpdateWorkspaceMemberRole(slug, memberID string, role int) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/members/%s/", slug, memberID)
-	payload := map[string]interface{}{
-		"role": role,
-	}
-
-	resp, err := planeWebPatch(url, payload)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] UpdateWorkspaceMemberRole 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] UpdateWorkspaceMemberRole 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("UpdateWorkspaceMemberRole 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// RemoveWorkspaceMember 移除工作空间成员
-func RemoveWorkspaceMember(slug, memberID string) error {
-	url := fmt.Sprintf("/api/workspaces/%s/members/%s/", slug, memberID)
-
-	resp, err := planeWebDelete(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] RemoveWorkspaceMember 请求失败", zap.Error(err))
-		return err
-	}
-
-	if resp.StatusCode != 204 {
-		zap.L().Error("[PlaneWeb] RemoveWorkspaceMember 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("RemoveWorkspaceMember 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// ListProjectMembers 列出项目成员
-func ListProjectMembers(slug, projectID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/members/", slug, projectID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] ListProjectMembers 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] ListProjectMembers 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("ListProjectMembers 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// AddProjectMember 添加项目成员
-func AddProjectMember(slug, projectID, memberID string, role int) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/members/", slug, projectID)
-	payload := map[string]interface{}{
-		"members": []map[string]interface{}{
-			{"member_id": memberID, "role": role},
-		},
-	}
-
-	resp, err := planeWebPost(url, payload)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] AddProjectMember 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if resp.StatusCode != 201 {
-		zap.L().Error("[PlaneWeb] AddProjectMember 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("AddProjectMember 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// GetProjectMember 获取项目成员详情
-func GetProjectMember(slug, projectID, memberID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/members/%s/", slug, projectID, memberID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] GetProjectMember 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] GetProjectMember 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("GetProjectMember 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// UpdateProjectMemberRole 更新项目成员角色
-func UpdateProjectMemberRole(slug, projectID, memberID string, role int) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/members/%s/", slug, projectID, memberID)
-	payload := map[string]interface{}{
-		"role": role,
-	}
-
-	resp, err := planeWebPatch(url, payload)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] UpdateProjectMemberRole 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] UpdateProjectMemberRole 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("UpdateProjectMemberRole 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// RemoveProjectMember 移除项目成员
-func RemoveProjectMember(slug, projectID, memberID string) error {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/members/%s/", slug, projectID, memberID)
-
-	resp, err := planeWebDelete(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] RemoveProjectMember 请求失败", zap.Error(err))
-		return err
-	}
-
-	if resp.StatusCode != 204 {
-		zap.L().Error("[PlaneWeb] RemoveProjectMember 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("RemoveProjectMember 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// GetCurrentUser 获取当前用户信息
-func GetCurrentUser() ([]byte, error) {
-	resp, err := planeWebGet("/api/users/me/")
-	if err != nil {
-		zap.L().Error("[PlaneWeb] GetCurrentUser 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] GetCurrentUser 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("GetCurrentUser 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// UpdateCurrentUser 更新当前用户信息
-func UpdateCurrentUser(data map[string]interface{}) ([]byte, error) {
-	resp, err := planeWebPatch("/api/users/me/", data)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] UpdateCurrentUser 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] UpdateCurrentUser 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("UpdateCurrentUser 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// ListCurrentWorkspaceMembers 列出当前工作空间的成员
-func ListCurrentWorkspaceMembers() ([]byte, error) {
-	return ListWorkspaceMembers(webWorkspaceSlug)
-}
-
-// ListCurrentProjectMembers 列出当前项目的成员
-func ListCurrentProjectMembers() ([]byte, error) {
-	return ListProjectMembers(webWorkspaceSlug, webProjectId)
-}

+ 0 - 297
service/repositories/api/plane_v2/plane_module.go

@@ -1,297 +0,0 @@
-package plane_v2
-
-import (
-	"fmt"
-
-	"go.uber.org/zap"
-)
-
-// ==================== Module API ====================
-
-// ListModules 列出模块
-func ListModules(slug, projectID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/", slug, projectID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] ListModules 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] ListModules 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("ListModules 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// CreateModule 创建模块
-func CreateModule(slug, projectID string, data map[string]interface{}) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/", slug, projectID)
-
-	resp, err := planeWebPost(url, data)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] CreateModule 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if resp.StatusCode != 201 {
-		zap.L().Error("[PlaneWeb] CreateModule 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("CreateModule 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// GetModule 获取模块详情
-func GetModule(slug, projectID, moduleID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/%s/", slug, projectID, moduleID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] GetModule 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] GetModule 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("GetModule 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// UpdateModule 更新模块
-func UpdateModule(slug, projectID, moduleID string, data map[string]interface{}) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/%s/", slug, projectID, moduleID)
-
-	resp, err := planeWebPatch(url, data)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] UpdateModule 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] UpdateModule 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("UpdateModule 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// DeleteModule 删除模块
-func DeleteModule(slug, projectID, moduleID string) error {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/%s/", slug, projectID, moduleID)
-
-	resp, err := planeWebDelete(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] DeleteModule 请求失败", zap.Error(err))
-		return err
-	}
-
-	if resp.StatusCode != 204 {
-		zap.L().Error("[PlaneWeb] DeleteModule 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("DeleteModule 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// ListModuleIssues 列出模块中的工作项
-func ListModuleIssues(slug, projectID, moduleID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/%s/issues/", slug, projectID, moduleID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] ListModuleIssues 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] ListModuleIssues 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("ListModuleIssues 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// AddIssuesToModule 添加工作项到模块
-func AddIssuesToModule(slug, projectID, moduleID string, issueIDs []string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/%s/issues/", slug, projectID, moduleID)
-	payload := map[string]interface{}{
-		"issues": issueIDs,
-	}
-
-	resp, err := planeWebPost(url, payload)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] AddIssuesToModule 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] AddIssuesToModule 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("AddIssuesToModule 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// RemoveIssueFromModule 从模块移除工作项
-func RemoveIssueFromModule(slug, projectID, moduleID, issueID string) error {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/%s/issues/%s/", slug, projectID, moduleID, issueID)
-
-	resp, err := planeWebDelete(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] RemoveIssueFromModule 请求失败", zap.Error(err))
-		return err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] RemoveIssueFromModule 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("RemoveIssueFromModule 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// ListModuleLinks 列出模块链接
-func ListModuleLinks(slug, projectID, moduleID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/%s/module-links/", slug, projectID, moduleID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] ListModuleLinks 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] ListModuleLinks 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("ListModuleLinks 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// CreateModuleLink 创建模块链接
-func CreateModuleLink(slug, projectID, moduleID string, data map[string]interface{}) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/%s/module-links/", slug, projectID, moduleID)
-
-	resp, err := planeWebPost(url, data)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] CreateModuleLink 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if resp.StatusCode != 201 {
-		zap.L().Error("[PlaneWeb] CreateModuleLink 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("CreateModuleLink 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// UpdateModuleLink 更新模块链接
-func UpdateModuleLink(slug, projectID, moduleID, linkID string, data map[string]interface{}) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/%s/module-links/%s/", slug, projectID, moduleID, linkID)
-
-	resp, err := planeWebPatch(url, data)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] UpdateModuleLink 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] UpdateModuleLink 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("UpdateModuleLink 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// DeleteModuleLink 删除模块链接
-func DeleteModuleLink(slug, projectID, moduleID, linkID string) error {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/%s/module-links/%s/", slug, projectID, moduleID, linkID)
-
-	resp, err := planeWebDelete(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] DeleteModuleLink 请求失败", zap.Error(err))
-		return err
-	}
-
-	if resp.StatusCode != 204 {
-		zap.L().Error("[PlaneWeb] DeleteModuleLink 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("DeleteModuleLink 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// ArchiveModule 归档模块
-func ArchiveModule(slug, projectID, moduleID string) error {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/%s/archive/", slug, projectID, moduleID)
-
-	resp, err := planeWebPost(url, map[string]interface{}{})
-	if err != nil {
-		zap.L().Error("[PlaneWeb] ArchiveModule 请求失败", zap.Error(err))
-		return err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] ArchiveModule 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("ArchiveModule 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// ListArchivedModules 列出已归档模块
-func ListArchivedModules(slug, projectID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/archived-modules/", slug, projectID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] ListArchivedModules 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] ListArchivedModules 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("ListArchivedModules 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// UnarchiveModule 取消归档模块
-func UnarchiveModule(slug, projectID, moduleID string) error {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/archived-modules/%s/", slug, projectID, moduleID)
-
-	resp, err := planeWebDelete(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] UnarchiveModule 请求失败", zap.Error(err))
-		return err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] UnarchiveModule 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("UnarchiveModule 失败: %s", resp.Status)
-	}
-
-	return nil
-}

+ 0 - 258
service/repositories/api/plane_v2/plane_page.go

@@ -1,258 +0,0 @@
-package plane_v2
-
-import (
-	"fmt"
-
-	"go.uber.org/zap"
-)
-
-// ==================== Page API ====================
-
-// ListPages 列出页面
-func ListPages(slug, projectID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/", slug, projectID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] ListPages 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] ListPages 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("ListPages 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// CreatePage 创建页面
-func CreatePage(slug, projectID string, data map[string]interface{}) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/", slug, projectID)
-
-	resp, err := planeWebPost(url, data)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] CreatePage 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if resp.StatusCode != 201 {
-		zap.L().Error("[PlaneWeb] CreatePage 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("CreatePage 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// GetPage 获取页面详情
-func GetPage(slug, projectID, pageID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/%s/", slug, projectID, pageID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] GetPage 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] GetPage 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("GetPage 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// UpdatePage 更新页面
-func UpdatePage(slug, projectID, pageID string, data map[string]interface{}) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/%s/", slug, projectID, pageID)
-
-	resp, err := planeWebPatch(url, data)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] UpdatePage 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] UpdatePage 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("UpdatePage 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// GetPageDescription 获取页面描述内容
-func GetPageDescription(slug, projectID, pageID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/%s/description/", slug, projectID, pageID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] GetPageDescription 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] GetPageDescription 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("GetPageDescription 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// UpdatePageDescription 更新页面描述内容
-func UpdatePageDescription(slug, projectID, pageID, descHTML string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/%s/description/", slug, projectID, pageID)
-	payload := map[string]interface{}{
-		"description_html": descHTML,
-	}
-
-	resp, err := planeWebPatch(url, payload)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] UpdatePageDescription 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] UpdatePageDescription 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("UpdatePageDescription 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// DeletePage 删除页面
-func DeletePage(slug, projectID, pageID string) error {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/%s/", slug, projectID, pageID)
-
-	resp, err := planeWebDelete(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] DeletePage 请求失败", zap.Error(err))
-		return err
-	}
-
-	if resp.StatusCode != 204 {
-		zap.L().Error("[PlaneWeb] DeletePage 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("DeletePage 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// ArchivePage 归档页面
-func ArchivePage(slug, projectID, pageID string) error {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/%s/archive/", slug, projectID, pageID)
-
-	resp, err := planeWebPost(url, map[string]interface{}{})
-	if err != nil {
-		zap.L().Error("[PlaneWeb] ArchivePage 请求失败", zap.Error(err))
-		return err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] ArchivePage 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("ArchivePage 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// UnarchivePage 取消归档页面
-func UnarchivePage(slug, projectID, pageID string) error {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/%s/archive/", slug, projectID, pageID)
-
-	resp, err := planeWebDelete(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] UnarchivePage 请求失败", zap.Error(err))
-		return err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] UnarchivePage 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("UnarchivePage 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// LockPage 锁定页面
-func LockPage(slug, projectID, pageID string) error {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/%s/lock/", slug, projectID, pageID)
-
-	resp, err := planeWebPost(url, map[string]interface{}{})
-	if err != nil {
-		zap.L().Error("[PlaneWeb] LockPage 请求失败", zap.Error(err))
-		return err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] LockPage 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("LockPage 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// UnlockPage 解锁页面
-func UnlockPage(slug, projectID, pageID string) error {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/%s/lock/", slug, projectID, pageID)
-
-	resp, err := planeWebDelete(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] UnlockPage 请求失败", zap.Error(err))
-		return err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] UnlockPage 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("UnlockPage 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// SetPageAccess 设置页面访问权限 (access: 0=私密, 1=公开)
-func SetPageAccess(slug, projectID, pageID string, access int) error {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/%s/access/", slug, projectID, pageID)
-
-	resp, err := planeWebPost(url, map[string]interface{}{"access": access})
-	if err != nil {
-		zap.L().Error("[PlaneWeb] SetPageAccess 请求失败", zap.Error(err))
-		return err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] SetPageAccess 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("SetPageAccess 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// DuplicatePage 复制页面
-func DuplicatePage(slug, projectID, pageID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/%s/duplicate/", slug, projectID, pageID)
-
-	resp, err := planeWebPost(url, map[string]interface{}{})
-	if err != nil {
-		zap.L().Error("[PlaneWeb] DuplicatePage 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] DuplicatePage 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("DuplicatePage 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}

+ 0 - 151
service/repositories/api/plane_v2/plane_project.go

@@ -1,151 +0,0 @@
-package plane_v2
-
-import (
-	"fmt"
-
-	"go.uber.org/zap"
-)
-
-// ==================== Project API ====================
-
-// ListProjects 列出项目
-func ListProjects(slug string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/", slug)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] ListProjects 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] ListProjects 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("ListProjects 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// CreateProject 创建项目
-func CreateProject(slug string, data map[string]interface{}) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/", slug)
-
-	resp, err := planeWebPost(url, data)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] CreateProject 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if resp.StatusCode != 201 {
-		zap.L().Error("[PlaneWeb] CreateProject 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("CreateProject 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// GetProject 获取项目详情
-func GetProject(slug, projectID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/", slug, projectID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] GetProject 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] GetProject 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("GetProject 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// UpdateProject 更新项目
-func UpdateProject(slug, projectID string, data map[string]interface{}) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/", slug, projectID)
-
-	resp, err := planeWebPatch(url, data)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] UpdateProject 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] UpdateProject 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("UpdateProject 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// DeleteProject 删除项目
-func DeleteProject(slug, projectID string) error {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/", slug, projectID)
-
-	resp, err := planeWebDelete(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] DeleteProject 请求失败", zap.Error(err))
-		return err
-	}
-
-	if resp.StatusCode != 204 {
-		zap.L().Error("[PlaneWeb] DeleteProject 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("DeleteProject 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// ArchiveProject 归档项目
-func ArchiveProject(slug, projectID string) error {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/archive/", slug, projectID)
-
-	resp, err := planeWebPost(url, map[string]interface{}{})
-	if err != nil {
-		zap.L().Error("[PlaneWeb] ArchiveProject 请求失败", zap.Error(err))
-		return err
-	}
-
-	if resp.StatusCode != 204 {
-		zap.L().Error("[PlaneWeb] ArchiveProject 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("ArchiveProject 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// UnarchiveProject 取消归档项目
-func UnarchiveProject(slug, projectID string) error {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/archive/", slug, projectID)
-
-	resp, err := planeWebDelete(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] UnarchiveProject 请求失败", zap.Error(err))
-		return err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] UnarchiveProject 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("UnarchiveProject 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// ListCurrentProjects 列出当前配置的工作空间下的项目
-func ListCurrentProjects() ([]byte, error) {
-	return ListProjects(webWorkspaceSlug)
-}
-
-// GetCurrentProject 获取当前配置的项目详情
-func GetCurrentProject() ([]byte, error) {
-	return GetProject(webWorkspaceSlug, webProjectId)
-}

+ 0 - 75
service/repositories/api/plane_v2/plane_relation.go

@@ -1,75 +0,0 @@
-package plane_v2
-
-import (
-	"fmt"
-
-	"go.uber.org/zap"
-)
-
-// ==================== Issue Relation API ====================
-
-// ListIssueRelations 列出工作项关系
-func ListIssueRelations(slug, projectID, issueID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/issue-relation/", slug, projectID, issueID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] ListIssueRelations 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] ListIssueRelations 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("ListIssueRelations 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// CreateIssueRelation 创建工作项关系
-// relationType: blocking / blocked_by / duplicate / relates_to / start_before / start_after / finish_before / finish_after
-func CreateIssueRelation(slug, projectID, issueID, relationType string, relatedIDs []string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/issue-relation/", slug, projectID, issueID)
-	payload := map[string]interface{}{
-		"relation_type": relationType,
-		"related_list":  relatedIDs,
-	}
-
-	resp, err := planeWebPost(url, payload)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] CreateIssueRelation 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if resp.StatusCode != 201 {
-		zap.L().Error("[PlaneWeb] CreateIssueRelation 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("CreateIssueRelation 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// RemoveIssueRelation 移除工作项关系
-func RemoveIssueRelation(slug, projectID, issueID, relationType, relatedID string) error {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/remove-relation/", slug, projectID, issueID)
-	payload := map[string]interface{}{
-		"relation_type": relationType,
-		"related_list":  []string{relatedID},
-	}
-
-	resp, err := planeWebPost(url, payload)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] RemoveIssueRelation 请求失败", zap.Error(err))
-		return err
-	}
-
-	if resp.StatusCode != 204 {
-		zap.L().Error("[PlaneWeb] RemoveIssueRelation 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("RemoveIssueRelation 失败: %s", resp.Status)
-	}
-
-	return nil
-}

+ 0 - 134
service/repositories/api/plane_v2/plane_state.go

@@ -1,134 +0,0 @@
-package plane_v2
-
-import (
-	"fmt"
-
-	"go.uber.org/zap"
-)
-
-// ==================== State API ====================
-
-// ListStates 列出状态
-func ListStates(slug, projectID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/states/", slug, projectID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] ListStates 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] ListStates 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("ListStates 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// CreateState 创建状态
-// group: backlog / unstarted / started / completed / cancelled
-func CreateState(slug, projectID, name, color, group string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/states/", slug, projectID)
-	payload := map[string]interface{}{
-		"name":  name,
-		"color": color,
-		"group": group,
-	}
-
-	resp, err := planeWebPost(url, payload)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] CreateState 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if resp.StatusCode != 201 {
-		zap.L().Error("[PlaneWeb] CreateState 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("CreateState 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// GetState 获取状态详情
-func GetState(slug, projectID, stateID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/states/%s/", slug, projectID, stateID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] GetState 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] GetState 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("GetState 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// UpdateState 更新状态
-func UpdateState(slug, projectID, stateID string, data map[string]interface{}) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/states/%s/", slug, projectID, stateID)
-
-	resp, err := planeWebPatch(url, data)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] UpdateState 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] UpdateState 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("UpdateState 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// DeleteState 删除状态
-func DeleteState(slug, projectID, stateID string) error {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/states/%s/", slug, projectID, stateID)
-
-	resp, err := planeWebDelete(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] DeleteState 请求失败", zap.Error(err))
-		return err
-	}
-
-	if resp.StatusCode != 204 {
-		zap.L().Error("[PlaneWeb] DeleteState 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("DeleteState 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// MarkStateDefault 设为默认状态
-func MarkStateDefault(slug, projectID, stateID string) error {
-	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/states/%s/mark-default/", slug, projectID, stateID)
-
-	resp, err := planeWebPost(url, map[string]interface{}{})
-	if err != nil {
-		zap.L().Error("[PlaneWeb] MarkStateDefault 请求失败", zap.Error(err))
-		return err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] MarkStateDefault 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("MarkStateDefault 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// ListCurrentStates 列出当前项目下的状态
-func ListCurrentStates() ([]byte, error) {
-	return ListStates(webWorkspaceSlug, webProjectId)
-}

+ 0 - 110
service/repositories/api/plane_v2/plane_sticky.go

@@ -1,110 +0,0 @@
-package plane_v2
-
-import (
-	"fmt"
-
-	"go.uber.org/zap"
-)
-
-// ==================== Sticky (便签) API ====================
-
-// ListStickies 列出便签
-func ListStickies(slug string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/stickies/", slug)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] ListStickies 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] ListStickies 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("ListStickies 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// CreateSticky 创建便签
-func CreateSticky(slug string, data map[string]interface{}) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/stickies/", slug)
-
-	resp, err := planeWebPost(url, data)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] CreateSticky 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if resp.StatusCode != 201 {
-		zap.L().Error("[PlaneWeb] CreateSticky 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("CreateSticky 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// GetSticky 获取便签详情
-func GetSticky(slug, stickyID string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/stickies/%s/", slug, stickyID)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] GetSticky 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] GetSticky 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("GetSticky 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// UpdateSticky 更新便签
-func UpdateSticky(slug, stickyID string, data map[string]interface{}) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/stickies/%s/", slug, stickyID)
-
-	resp, err := planeWebPatch(url, data)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] UpdateSticky 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] UpdateSticky 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("UpdateSticky 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// DeleteSticky 删除便签
-func DeleteSticky(slug, stickyID string) error {
-	url := fmt.Sprintf("/api/workspaces/%s/stickies/%s/", slug, stickyID)
-
-	resp, err := planeWebDelete(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] DeleteSticky 请求失败", zap.Error(err))
-		return err
-	}
-
-	if resp.StatusCode != 204 {
-		zap.L().Error("[PlaneWeb] DeleteSticky 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("DeleteSticky 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// ListCurrentStickies 列出当前工作空间的便签
-func ListCurrentStickies() ([]byte, error) {
-	return ListStickies(webWorkspaceSlug)
-}

+ 122 - 0
service/repositories/api/plane_v2/plane_v2_api/asset.go

@@ -0,0 +1,122 @@
+package plane_v2_api
+
+import (
+	"encoding/json"
+	"fmt"
+
+	plane "fwh-toolbox-server/repositories/api/plane_v2"
+)
+
+// CreateProjectAsset 创建项目资源(获取预签名 URL)
+func CreateProjectAsset(c *plane.PlaneClient, slug, projectID, fileName, mimeType string, size int64, entityType string) (*plane.AssetUploadResult, error) {
+	url := fmt.Sprintf("/api/assets/v2/workspaces/%s/projects/%s/", slug, projectID)
+	payload := map[string]interface{}{
+		"name":              fileName,
+		"type":              mimeType,
+		"size":              size,
+		"entity_type":       entityType,
+		"entity_identifier": "",
+	}
+
+	resp, err := c.Post(url, payload)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("CreateProjectAsset 失败: %s", resp.Status)
+	}
+
+	var result plane.AssetUploadResult
+	if err := json.Unmarshal(resp.Body, &result); err != nil {
+		return nil, fmt.Errorf("解析响应失败: %w", err)
+	}
+	return &result, nil
+}
+
+// ConfirmProjectAssetUpload 确认项目资源上传完成
+func ConfirmProjectAssetUpload(c *plane.PlaneClient, slug, projectID, assetID string) error {
+	url := fmt.Sprintf("/api/assets/v2/workspaces/%s/projects/%s/%s/", slug, projectID, assetID)
+
+	resp, err := c.Patch(url, map[string]interface{}{})
+	if err != nil {
+		return err
+	}
+	if resp.StatusCode != 204 && !plane.IsSuccess(resp.StatusCode) {
+		return fmt.Errorf("ConfirmProjectAssetUpload 失败: %s", resp.Status)
+	}
+	return nil
+}
+
+// GetProjectAsset 获取项目资源
+func GetProjectAsset(c *plane.PlaneClient, slug, projectID, assetID string) ([]byte, error) {
+	url := fmt.Sprintf("/api/assets/v2/workspaces/%s/projects/%s/%s/", slug, projectID, assetID)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	return resp.Body, nil
+}
+
+// GetProjectAssetURL 获取项目资源的预签名访问 URL
+func GetProjectAssetURL(c *plane.PlaneClient, slug, projectID, assetID string) (string, error) {
+	url := fmt.Sprintf("/api/assets/v2/workspaces/%s/projects/%s/%s/", slug, projectID, assetID)
+	return c.GetRedirectLocation(url)
+}
+
+// DeleteProjectAsset 删除项目资源
+func DeleteProjectAsset(c *plane.PlaneClient, slug, projectID, assetID string) error {
+	url := fmt.Sprintf("/api/assets/v2/workspaces/%s/projects/%s/%s/", slug, projectID, assetID)
+
+	resp, err := c.Delete(url)
+	if err != nil {
+		return err
+	}
+	if resp.StatusCode != 204 {
+		return fmt.Errorf("DeleteProjectAsset 失败: %s", resp.Status)
+	}
+	return nil
+}
+
+// BindAssetsToEntity 批量绑定资源到实体
+func BindAssetsToEntity(c *plane.PlaneClient, slug, projectID, entityID string, assetIDs []string) error {
+	url := fmt.Sprintf("/api/assets/v2/workspaces/%s/projects/%s/%s/bulk/", slug, projectID, entityID)
+	payload := map[string]interface{}{
+		"asset_ids": assetIDs,
+	}
+
+	resp, err := c.Post(url, payload)
+	if err != nil {
+		return err
+	}
+	if resp.StatusCode != 204 && !plane.IsSuccess(resp.StatusCode) {
+		return fmt.Errorf("BindAssetsToEntity 失败: %s", resp.Status)
+	}
+	return nil
+}
+
+// UploadToS3 上传文件到 S3(使用预签名 POST)
+func UploadToS3(c *plane.PlaneClient, uploadURL string, fields map[string]string, fileData []byte, fileName string) error {
+	return c.PostS3(uploadURL, fields, fileData, fileName)
+}
+
+// UploadProjectAsset 完整上传项目资源流程:创建 → S3 上传 → 确认
+func UploadProjectAsset(c *plane.PlaneClient, slug, projectID, fileName, mimeType string, fileData []byte, entityType string) (*plane.AssetUploadResult, error) {
+	// Step 1: 创建资源
+	result, err := CreateProjectAsset(c, slug, projectID, fileName, mimeType, int64(len(fileData)), entityType)
+	if err != nil {
+		return nil, fmt.Errorf("创建资源失败: %w", err)
+	}
+
+	// Step 2: 上传到 S3
+	if err := UploadToS3(c, result.UploadData.URL, result.UploadData.Fields, fileData, fileName); err != nil {
+		return nil, fmt.Errorf("上传文件到S3失败: %w", err)
+	}
+
+	// Step 3: 确认上传完成
+	if err := ConfirmProjectAssetUpload(c, slug, projectID, result.AssetID); err != nil {
+		return nil, fmt.Errorf("确认上传失败: %w", err)
+	}
+
+	return result, nil
+}

+ 83 - 0
service/repositories/api/plane_v2/plane_v2_api/comment.go

@@ -0,0 +1,83 @@
+package plane_v2_api
+
+import (
+	"fmt"
+
+	plane "fwh-toolbox-server/repositories/api/plane_v2"
+)
+
+// ListComments 列出工作项评论
+func ListComments(c *plane.PlaneClient, slug, projectID, issueID string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/comments/", slug, projectID, issueID)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("ListComments 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// CreateComment 创建评论
+func CreateComment(c *plane.PlaneClient, slug, projectID, issueID, commentHTML string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/comments/", slug, projectID, issueID)
+	payload := map[string]interface{}{
+		"comment_html": commentHTML,
+	}
+
+	resp, err := c.Post(url, payload)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("CreateComment 失败: %s, body=%s", resp.Status, string(resp.Body))
+	}
+	return resp.Body, nil
+}
+
+// GetComment 获取评论详情
+func GetComment(c *plane.PlaneClient, slug, projectID, issueID, commentID string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/comments/%s/", slug, projectID, issueID, commentID)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("GetComment 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// UpdateComment 更新评论
+func UpdateComment(c *plane.PlaneClient, slug, projectID, issueID, commentID, commentHTML string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/comments/%s/", slug, projectID, issueID, commentID)
+	payload := map[string]interface{}{
+		"comment_html": commentHTML,
+	}
+
+	resp, err := c.Patch(url, payload)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("UpdateComment 失败: %s, body=%s", resp.Status, string(resp.Body))
+	}
+	return resp.Body, nil
+}
+
+// DeleteComment 删除评论
+func DeleteComment(c *plane.PlaneClient, slug, projectID, issueID, commentID string) error {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/comments/%s/", slug, projectID, issueID, commentID)
+
+	resp, err := c.Delete(url)
+	if err != nil {
+		return err
+	}
+	if resp.StatusCode != 204 {
+		return fmt.Errorf("DeleteComment 失败: %s", resp.Status)
+	}
+	return nil
+}

+ 150 - 0
service/repositories/api/plane_v2/plane_v2_api/cycle.go

@@ -0,0 +1,150 @@
+package plane_v2_api
+
+import (
+	"fmt"
+
+	plane "fwh-toolbox-server/repositories/api/plane_v2"
+)
+
+// ListCycles 列出项目周期
+func ListCycles(c *plane.PlaneClient, slug, projectID string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/", slug, projectID)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("ListCycles 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// CreateCycle 创建周期
+func CreateCycle(c *plane.PlaneClient, slug, projectID string, data map[string]interface{}) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/", slug, projectID)
+
+	resp, err := c.Post(url, data)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("CreateCycle 失败: %s, body=%s", resp.Status, string(resp.Body))
+	}
+	return resp.Body, nil
+}
+
+// GetCycle 获取周期详情
+func GetCycle(c *plane.PlaneClient, slug, projectID, cycleID string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/", slug, projectID, cycleID)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("GetCycle 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// UpdateCycle 更新周期
+func UpdateCycle(c *plane.PlaneClient, slug, projectID, cycleID string, data map[string]interface{}) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/", slug, projectID, cycleID)
+
+	resp, err := c.Patch(url, data)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("UpdateCycle 失败: %s, body=%s", resp.Status, string(resp.Body))
+	}
+	return resp.Body, nil
+}
+
+// DeleteCycle 删除周期
+func DeleteCycle(c *plane.PlaneClient, slug, projectID, cycleID string) error {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/", slug, projectID, cycleID)
+
+	resp, err := c.Delete(url)
+	if err != nil {
+		return err
+	}
+	if resp.StatusCode != 204 {
+		return fmt.Errorf("DeleteCycle 失败: %s", resp.Status)
+	}
+	return nil
+}
+
+// ListCycleIssues 列出周期工作项
+func ListCycleIssues(c *plane.PlaneClient, slug, projectID, cycleID string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/cycle-issues/", slug, projectID, cycleID)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("ListCycleIssues 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// AddIssuesToCycle 添加工作项到周期
+func AddIssuesToCycle(c *plane.PlaneClient, slug, projectID, cycleID string, issueIDs []string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/cycle-issues/", slug, projectID, cycleID)
+	payload := map[string]interface{}{
+		"issues": issueIDs,
+	}
+
+	resp, err := c.Post(url, payload)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("AddIssuesToCycle 失败: %s, body=%s", resp.Status, string(resp.Body))
+	}
+	return resp.Body, nil
+}
+
+// RemoveIssueFromCycle 从周期移除工作项
+func RemoveIssueFromCycle(c *plane.PlaneClient, slug, projectID, cycleID, issueID string) error {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/cycle-issues/%s/", slug, projectID, cycleID, issueID)
+
+	resp, err := c.Delete(url)
+	if err != nil {
+		return err
+	}
+	if resp.StatusCode != 204 {
+		return fmt.Errorf("RemoveIssueFromCycle 失败: %s", resp.Status)
+	}
+	return nil
+}
+
+// ArchiveCycle 归档周期
+func ArchiveCycle(c *plane.PlaneClient, slug, projectID, cycleID string) error {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/archive/", slug, projectID, cycleID)
+
+	resp, err := c.Post(url, nil)
+	if err != nil {
+		return err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return fmt.Errorf("ArchiveCycle 失败: %s", resp.Status)
+	}
+	return nil
+}
+
+// UnarchiveCycle 取消归档周期
+func UnarchiveCycle(c *plane.PlaneClient, slug, projectID, cycleID string) error {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/archive/", slug, projectID, cycleID)
+
+	resp, err := c.Delete(url)
+	if err != nil {
+		return err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return fmt.Errorf("UnarchiveCycle 失败: %s", resp.Status)
+	}
+	return nil
+}

+ 104 - 0
service/repositories/api/plane_v2/plane_v2_api/intake.go

@@ -0,0 +1,104 @@
+package plane_v2_api
+
+import (
+	"encoding/json"
+	"fmt"
+
+	plane "fwh-toolbox-server/repositories/api/plane_v2"
+
+	"go.uber.org/zap"
+)
+
+// ListIntakeIssues 列出收件箱工作项
+func ListIntakeIssues(c *plane.PlaneClient, slug, projectID, status string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/intake-issues/", slug, projectID)
+	if status != "" {
+		url += "?status=" + status
+	}
+
+	resp, err := c.Get(url)
+	if err != nil {
+		zap.L().Error("[PlaneAPI] ListIntakeIssues 请求失败", zap.Error(err))
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("ListIntakeIssues 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// CreateIntakeIssue 创建收件箱工作项
+func CreateIntakeIssue(c *plane.PlaneClient, slug, projectID string, issue plane.IntakeIssueRequest) (*plane.IntakeIssueResponse, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/intake-issues/", slug, projectID)
+	payload := map[string]any{
+		"source": "IN_APP",
+		"issue":  issue,
+	}
+
+	resp, err := c.Post(url, payload)
+	if err != nil {
+		zap.L().Error("[PlaneAPI] CreateIntakeIssue 请求失败", zap.Error(err))
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("CreateIntakeIssue 失败: %s, body=%s", resp.Status, string(resp.Body))
+	}
+
+	var result plane.IntakeIssueResponse
+	if err := json.Unmarshal(resp.Body, &result); err != nil {
+		return nil, fmt.Errorf("响应解析失败: %w", err)
+	}
+	return &result, nil
+}
+
+// GetIntakeIssue 获取收件箱工作项详情
+func GetIntakeIssue(c *plane.PlaneClient, slug, projectID, issueID string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/intake-issues/%s/?expand=issue_inbox", slug, projectID, issueID)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("GetIntakeIssue 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// UpdateIntakeIssue 更新收件箱工作项
+func UpdateIntakeIssue(c *plane.PlaneClient, slug, projectID, issueID string, data map[string]any) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/intake-issues/%s/", slug, projectID, issueID)
+
+	resp, err := c.Patch(url, data)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("UpdateIntakeIssue 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// AcceptIntakeIssue 接受收件箱工作项
+func AcceptIntakeIssue(c *plane.PlaneClient, slug, projectID, issueID string) ([]byte, error) {
+	return UpdateIntakeIssue(c, slug, projectID, issueID, map[string]any{"status": 1})
+}
+
+// RejectIntakeIssue 拒绝收件箱工作项
+func RejectIntakeIssue(c *plane.PlaneClient, slug, projectID, issueID string) ([]byte, error) {
+	return UpdateIntakeIssue(c, slug, projectID, issueID, map[string]any{"status": -1})
+}
+
+// DeleteIntakeIssue 删除收件箱工作项
+func DeleteIntakeIssue(c *plane.PlaneClient, slug, projectID, issueID string) error {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/intake-issues/%s/", slug, projectID, issueID)
+
+	resp, err := c.Delete(url)
+	if err != nil {
+		return err
+	}
+	if resp.StatusCode != 204 {
+		return fmt.Errorf("DeleteIntakeIssue 失败: %s", resp.Status)
+	}
+	return nil
+}

+ 144 - 0
service/repositories/api/plane_v2/plane_v2_api/issue.go

@@ -0,0 +1,144 @@
+package plane_v2_api
+
+import (
+	"fmt"
+
+	plane "fwh-toolbox-server/repositories/api/plane_v2"
+
+	"go.uber.org/zap"
+)
+
+// ListIssues 列出项目工作项
+func ListIssues(c *plane.PlaneClient, slug, projectID string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/", slug, projectID)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		zap.L().Error("[PlaneAPI] ListIssues 请求失败", zap.Error(err))
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("ListIssues 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// CreateIssue 创建工作项
+func CreateIssue(c *plane.PlaneClient, slug, projectID string, data map[string]interface{}) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/", slug, projectID)
+
+	resp, err := c.Post(url, data)
+	if err != nil {
+		zap.L().Error("[PlaneAPI] CreateIssue 请求失败", zap.Error(err))
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("CreateIssue 失败: %s, body=%s", resp.Status, string(resp.Body))
+	}
+	return resp.Body, nil
+}
+
+// GetIssue 获取工作项详情
+func GetIssue(c *plane.PlaneClient, slug, projectID, issueID string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/", slug, projectID, issueID)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		zap.L().Error("[PlaneAPI] GetIssue 请求失败", zap.Error(err))
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("GetIssue 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// UpdateIssue 更新工作项
+func UpdateIssue(c *plane.PlaneClient, slug, projectID, issueID string, data map[string]interface{}) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/", slug, projectID, issueID)
+
+	resp, err := c.Patch(url, data)
+	if err != nil {
+		zap.L().Error("[PlaneAPI] UpdateIssue 请求失败", zap.Error(err))
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("UpdateIssue 失败: %s, body=%s", resp.Status, string(resp.Body))
+	}
+	return resp.Body, nil
+}
+
+// DeleteIssue 删除工作项
+func DeleteIssue(c *plane.PlaneClient, slug, projectID, issueID string) error {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/", slug, projectID, issueID)
+
+	resp, err := c.Delete(url)
+	if err != nil {
+		zap.L().Error("[PlaneAPI] DeleteIssue 请求失败", zap.Error(err))
+		return err
+	}
+	if resp.StatusCode != 204 {
+		return fmt.Errorf("DeleteIssue 失败: %s", resp.Status)
+	}
+	return nil
+}
+
+// GetSubIssues 获取子工作项
+func GetSubIssues(c *plane.PlaneClient, slug, projectID, issueID string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/sub-issues/", slug, projectID, issueID)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		zap.L().Error("[PlaneAPI] GetSubIssues 请求失败", zap.Error(err))
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("GetSubIssues 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// ArchiveIssue 归档工作项
+func ArchiveIssue(c *plane.PlaneClient, slug, projectID, issueID string) error {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/archive/", slug, projectID, issueID)
+
+	resp, err := c.Post(url, nil)
+	if err != nil {
+		zap.L().Error("[PlaneAPI] ArchiveIssue 请求失败", zap.Error(err))
+		return err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return fmt.Errorf("ArchiveIssue 失败: %s", resp.Status)
+	}
+	return nil
+}
+
+// UnarchiveIssue 取消归档工作项
+func UnarchiveIssue(c *plane.PlaneClient, slug, projectID, issueID string) error {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/archive/", slug, projectID, issueID)
+
+	resp, err := c.Delete(url)
+	if err != nil {
+		zap.L().Error("[PlaneAPI] UnarchiveIssue 请求失败", zap.Error(err))
+		return err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return fmt.Errorf("UnarchiveIssue 失败: %s", resp.Status)
+	}
+	return nil
+}
+
+// GetIssueActivity 获取工作项活动历史
+func GetIssueActivity(c *plane.PlaneClient, slug, projectID, issueID string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/history/?activity_type=issue-property", slug, projectID, issueID)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		zap.L().Error("[PlaneAPI] GetIssueActivity 请求失败", zap.Error(err))
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("GetIssueActivity 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}

+ 81 - 0
service/repositories/api/plane_v2/plane_v2_api/label.go

@@ -0,0 +1,81 @@
+package plane_v2_api
+
+import (
+	"fmt"
+
+	plane "fwh-toolbox-server/repositories/api/plane_v2"
+)
+
+// ListLabels 列出项目标签
+func ListLabels(c *plane.PlaneClient, slug, projectID string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/labels/", slug, projectID)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("ListLabels 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// CreateLabel 创建标签
+func CreateLabel(c *plane.PlaneClient, slug, projectID, name, color string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/labels/", slug, projectID)
+	payload := map[string]interface{}{
+		"name":  name,
+		"color": color,
+	}
+
+	resp, err := c.Post(url, payload)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("CreateLabel 失败: %s, body=%s", resp.Status, string(resp.Body))
+	}
+	return resp.Body, nil
+}
+
+// GetLabel 获取标签详情
+func GetLabel(c *plane.PlaneClient, slug, projectID, labelID string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/labels/%s/", slug, projectID, labelID)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("GetLabel 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// UpdateLabel 更新标签
+func UpdateLabel(c *plane.PlaneClient, slug, projectID, labelID string, data map[string]interface{}) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/labels/%s/", slug, projectID, labelID)
+
+	resp, err := c.Patch(url, data)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("UpdateLabel 失败: %s, body=%s", resp.Status, string(resp.Body))
+	}
+	return resp.Body, nil
+}
+
+// DeleteLabel 删除标签
+func DeleteLabel(c *plane.PlaneClient, slug, projectID, labelID string) error {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/labels/%s/", slug, projectID, labelID)
+
+	resp, err := c.Delete(url)
+	if err != nil {
+		return err
+	}
+	if resp.StatusCode != 204 {
+		return fmt.Errorf("DeleteLabel 失败: %s", resp.Status)
+	}
+	return nil
+}

+ 67 - 0
service/repositories/api/plane_v2/plane_v2_api/link.go

@@ -0,0 +1,67 @@
+package plane_v2_api
+
+import (
+	"fmt"
+
+	plane "fwh-toolbox-server/repositories/api/plane_v2"
+)
+
+// ListIssueLinks 列出工作项链接
+func ListIssueLinks(c *plane.PlaneClient, slug, projectID, issueID string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/links/", slug, projectID, issueID)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("ListIssueLinks 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// CreateIssueLink 创建工作项链接
+func CreateIssueLink(c *plane.PlaneClient, slug, projectID, issueID, linkURL, title string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/links/", slug, projectID, issueID)
+	payload := map[string]interface{}{
+		"url":   linkURL,
+		"title": title,
+	}
+
+	resp, err := c.Post(url, payload)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("CreateIssueLink 失败: %s, body=%s", resp.Status, string(resp.Body))
+	}
+	return resp.Body, nil
+}
+
+// UpdateIssueLink 更新工作项链接
+func UpdateIssueLink(c *plane.PlaneClient, slug, projectID, issueID, linkID string, data map[string]interface{}) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/links/%s/", slug, projectID, issueID, linkID)
+
+	resp, err := c.Patch(url, data)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("UpdateIssueLink 失败: %s, body=%s", resp.Status, string(resp.Body))
+	}
+	return resp.Body, nil
+}
+
+// DeleteIssueLink 删除工作项链接
+func DeleteIssueLink(c *plane.PlaneClient, slug, projectID, issueID, linkID string) error {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/links/%s/", slug, projectID, issueID, linkID)
+
+	resp, err := c.Delete(url)
+	if err != nil {
+		return err
+	}
+	if resp.StatusCode != 204 {
+		return fmt.Errorf("DeleteIssueLink 失败: %s", resp.Status)
+	}
+	return nil
+}

+ 143 - 0
service/repositories/api/plane_v2/plane_v2_api/member.go

@@ -0,0 +1,143 @@
+package plane_v2_api
+
+import (
+	"fmt"
+
+	plane "fwh-toolbox-server/repositories/api/plane_v2"
+)
+
+// ListWorkspaceMembers 列出工作空间成员
+func ListWorkspaceMembers(c *plane.PlaneClient, slug string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/members/", slug)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("ListWorkspaceMembers 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// GetWorkspaceMember 获取工作空间成员详情
+func GetWorkspaceMember(c *plane.PlaneClient, slug, memberID string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/members/%s/", slug, memberID)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("GetWorkspaceMember 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// UpdateWorkspaceMemberRole 更新工作空间成员角色
+func UpdateWorkspaceMemberRole(c *plane.PlaneClient, slug, memberID string, role int) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/members/%s/", slug, memberID)
+	payload := map[string]interface{}{
+		"role": role,
+	}
+
+	resp, err := c.Patch(url, payload)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("UpdateWorkspaceMemberRole 失败: %s, body=%s", resp.Status, string(resp.Body))
+	}
+	return resp.Body, nil
+}
+
+// RemoveWorkspaceMember 移除工作空间成员
+func RemoveWorkspaceMember(c *plane.PlaneClient, slug, memberID string) error {
+	url := fmt.Sprintf("/api/workspaces/%s/members/%s/", slug, memberID)
+
+	resp, err := c.Delete(url)
+	if err != nil {
+		return err
+	}
+	if resp.StatusCode != 204 {
+		return fmt.Errorf("RemoveWorkspaceMember 失败: %s", resp.Status)
+	}
+	return nil
+}
+
+// ListProjectMembers 列出项目成员
+func ListProjectMembers(c *plane.PlaneClient, slug, projectID string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/members/", slug, projectID)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("ListProjectMembers 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// AddProjectMember 添加项目成员
+func AddProjectMember(c *plane.PlaneClient, slug, projectID, memberID string, role int) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/members/", slug, projectID)
+	payload := map[string]interface{}{
+		"member": memberID,
+		"role":   role,
+	}
+
+	resp, err := c.Post(url, payload)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("AddProjectMember 失败: %s, body=%s", resp.Status, string(resp.Body))
+	}
+	return resp.Body, nil
+}
+
+// GetProjectMember 获取项目成员详情
+func GetProjectMember(c *plane.PlaneClient, slug, projectID, memberID string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/members/%s/", slug, projectID, memberID)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("GetProjectMember 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// UpdateProjectMemberRole 更新项目成员角色
+func UpdateProjectMemberRole(c *plane.PlaneClient, slug, projectID, memberID string, role int) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/members/%s/", slug, projectID, memberID)
+	payload := map[string]interface{}{
+		"role": role,
+	}
+
+	resp, err := c.Patch(url, payload)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("UpdateProjectMemberRole 失败: %s, body=%s", resp.Status, string(resp.Body))
+	}
+	return resp.Body, nil
+}
+
+// RemoveProjectMember 移除项目成员
+func RemoveProjectMember(c *plane.PlaneClient, slug, projectID, memberID string) error {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/members/%s/", slug, projectID, memberID)
+
+	resp, err := c.Delete(url)
+	if err != nil {
+		return err
+	}
+	if resp.StatusCode != 204 {
+		return fmt.Errorf("RemoveProjectMember 失败: %s", resp.Status)
+	}
+	return nil
+}

+ 150 - 0
service/repositories/api/plane_v2/plane_v2_api/module.go

@@ -0,0 +1,150 @@
+package plane_v2_api
+
+import (
+	"fmt"
+
+	plane "fwh-toolbox-server/repositories/api/plane_v2"
+)
+
+// ListModules 列出项目模块
+func ListModules(c *plane.PlaneClient, slug, projectID string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/", slug, projectID)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("ListModules 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// CreateModule 创建模块
+func CreateModule(c *plane.PlaneClient, slug, projectID string, data map[string]interface{}) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/", slug, projectID)
+
+	resp, err := c.Post(url, data)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("CreateModule 失败: %s, body=%s", resp.Status, string(resp.Body))
+	}
+	return resp.Body, nil
+}
+
+// GetModule 获取模块详情
+func GetModule(c *plane.PlaneClient, slug, projectID, moduleID string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/%s/", slug, projectID, moduleID)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("GetModule 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// UpdateModule 更新模块
+func UpdateModule(c *plane.PlaneClient, slug, projectID, moduleID string, data map[string]interface{}) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/%s/", slug, projectID, moduleID)
+
+	resp, err := c.Patch(url, data)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("UpdateModule 失败: %s, body=%s", resp.Status, string(resp.Body))
+	}
+	return resp.Body, nil
+}
+
+// DeleteModule 删除模块
+func DeleteModule(c *plane.PlaneClient, slug, projectID, moduleID string) error {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/%s/", slug, projectID, moduleID)
+
+	resp, err := c.Delete(url)
+	if err != nil {
+		return err
+	}
+	if resp.StatusCode != 204 {
+		return fmt.Errorf("DeleteModule 失败: %s", resp.Status)
+	}
+	return nil
+}
+
+// ListModuleIssues 列出模块工作项
+func ListModuleIssues(c *plane.PlaneClient, slug, projectID, moduleID string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/%s/module-issues/", slug, projectID, moduleID)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("ListModuleIssues 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// AddIssuesToModule 添加工作项到模块
+func AddIssuesToModule(c *plane.PlaneClient, slug, projectID, moduleID string, issueIDs []string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/%s/module-issues/", slug, projectID, moduleID)
+	payload := map[string]interface{}{
+		"issues": issueIDs,
+	}
+
+	resp, err := c.Post(url, payload)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("AddIssuesToModule 失败: %s, body=%s", resp.Status, string(resp.Body))
+	}
+	return resp.Body, nil
+}
+
+// RemoveIssueFromModule 从模块移除工作项
+func RemoveIssueFromModule(c *plane.PlaneClient, slug, projectID, moduleID, issueID string) error {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/%s/module-issues/%s/", slug, projectID, moduleID, issueID)
+
+	resp, err := c.Delete(url)
+	if err != nil {
+		return err
+	}
+	if resp.StatusCode != 204 {
+		return fmt.Errorf("RemoveIssueFromModule 失败: %s", resp.Status)
+	}
+	return nil
+}
+
+// ArchiveModule 归档模块
+func ArchiveModule(c *plane.PlaneClient, slug, projectID, moduleID string) error {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/%s/archive/", slug, projectID, moduleID)
+
+	resp, err := c.Post(url, nil)
+	if err != nil {
+		return err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return fmt.Errorf("ArchiveModule 失败: %s", resp.Status)
+	}
+	return nil
+}
+
+// UnarchiveModule 取消归档模块
+func UnarchiveModule(c *plane.PlaneClient, slug, projectID, moduleID string) error {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/%s/archive/", slug, projectID, moduleID)
+
+	resp, err := c.Delete(url)
+	if err != nil {
+		return err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return fmt.Errorf("UnarchiveModule 失败: %s", resp.Status)
+	}
+	return nil
+}

+ 119 - 0
service/repositories/api/plane_v2/plane_v2_api/page.go

@@ -0,0 +1,119 @@
+package plane_v2_api
+
+import (
+	"fmt"
+
+	plane "fwh-toolbox-server/repositories/api/plane_v2"
+)
+
+// ListPages 列出项目页面
+func ListPages(c *plane.PlaneClient, slug, projectID string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/", slug, projectID)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("ListPages 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// CreatePage 创建页面
+func CreatePage(c *plane.PlaneClient, slug, projectID string, data map[string]interface{}) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/", slug, projectID)
+
+	resp, err := c.Post(url, data)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("CreatePage 失败: %s, body=%s", resp.Status, string(resp.Body))
+	}
+	return resp.Body, nil
+}
+
+// GetPage 获取页面详情
+func GetPage(c *plane.PlaneClient, slug, projectID, pageID string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/%s/", slug, projectID, pageID)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("GetPage 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// UpdatePage 更新页面
+func UpdatePage(c *plane.PlaneClient, slug, projectID, pageID string, data map[string]interface{}) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/%s/", slug, projectID, pageID)
+
+	resp, err := c.Patch(url, data)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("UpdatePage 失败: %s, body=%s", resp.Status, string(resp.Body))
+	}
+	return resp.Body, nil
+}
+
+// DeletePage 删除页面
+func DeletePage(c *plane.PlaneClient, slug, projectID, pageID string) error {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/%s/", slug, projectID, pageID)
+
+	resp, err := c.Delete(url)
+	if err != nil {
+		return err
+	}
+	if resp.StatusCode != 204 {
+		return fmt.Errorf("DeletePage 失败: %s", resp.Status)
+	}
+	return nil
+}
+
+// ArchivePage 归档页面
+func ArchivePage(c *plane.PlaneClient, slug, projectID, pageID string) error {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/%s/archive/", slug, projectID, pageID)
+
+	resp, err := c.Post(url, nil)
+	if err != nil {
+		return err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return fmt.Errorf("ArchivePage 失败: %s", resp.Status)
+	}
+	return nil
+}
+
+// UnarchivePage 取消归档页面
+func UnarchivePage(c *plane.PlaneClient, slug, projectID, pageID string) error {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/%s/archive/", slug, projectID, pageID)
+
+	resp, err := c.Delete(url)
+	if err != nil {
+		return err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return fmt.Errorf("UnarchivePage 失败: %s", resp.Status)
+	}
+	return nil
+}
+
+// GetPageDescription 获取页面描述
+func GetPageDescription(c *plane.PlaneClient, slug, projectID, pageID string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/%s/description/", slug, projectID, pageID)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("GetPageDescription 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}

+ 105 - 0
service/repositories/api/plane_v2/plane_v2_api/project.go

@@ -0,0 +1,105 @@
+package plane_v2_api
+
+import (
+	"fmt"
+
+	plane "fwh-toolbox-server/repositories/api/plane_v2"
+)
+
+// ListProjects 列出工作空间下的所有项目
+func ListProjects(c *plane.PlaneClient, slug string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/", slug)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("ListProjects 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// CreateProject 创建项目
+func CreateProject(c *plane.PlaneClient, slug string, data map[string]interface{}) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/", slug)
+
+	resp, err := c.Post(url, data)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("CreateProject 失败: %s, body=%s", resp.Status, string(resp.Body))
+	}
+	return resp.Body, nil
+}
+
+// GetProject 获取项目详情
+func GetProject(c *plane.PlaneClient, slug, projectID string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/", slug, projectID)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("GetProject 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// UpdateProject 更新项目
+func UpdateProject(c *plane.PlaneClient, slug, projectID string, data map[string]interface{}) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/", slug, projectID)
+
+	resp, err := c.Patch(url, data)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("UpdateProject 失败: %s, body=%s", resp.Status, string(resp.Body))
+	}
+	return resp.Body, nil
+}
+
+// DeleteProject 删除项目
+func DeleteProject(c *plane.PlaneClient, slug, projectID string) error {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/", slug, projectID)
+
+	resp, err := c.Delete(url)
+	if err != nil {
+		return err
+	}
+	if resp.StatusCode != 204 {
+		return fmt.Errorf("DeleteProject 失败: %s", resp.Status)
+	}
+	return nil
+}
+
+// ArchiveProject 归档项目
+func ArchiveProject(c *plane.PlaneClient, slug, projectID string) error {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/archive/", slug, projectID)
+
+	resp, err := c.Post(url, nil)
+	if err != nil {
+		return err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return fmt.Errorf("ArchiveProject 失败: %s", resp.Status)
+	}
+	return nil
+}
+
+// UnarchiveProject 取消归档项目
+func UnarchiveProject(c *plane.PlaneClient, slug, projectID string) error {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/archive/", slug, projectID)
+
+	resp, err := c.Delete(url)
+	if err != nil {
+		return err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return fmt.Errorf("UnarchiveProject 失败: %s", resp.Status)
+	}
+	return nil
+}

+ 53 - 0
service/repositories/api/plane_v2/plane_v2_api/relation.go

@@ -0,0 +1,53 @@
+package plane_v2_api
+
+import (
+	"fmt"
+
+	plane "fwh-toolbox-server/repositories/api/plane_v2"
+)
+
+// ListIssueRelations 列出工作项关联
+func ListIssueRelations(c *plane.PlaneClient, slug, projectID, issueID string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/relations/", slug, projectID, issueID)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("ListIssueRelations 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// CreateIssueRelation 创建工作项关联
+func CreateIssueRelation(c *plane.PlaneClient, slug, projectID, issueID, relationType string, relatedIDs []string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/relations/", slug, projectID, issueID)
+	payload := map[string]interface{}{
+		"relation_type":  relationType,
+		"related_issues": relatedIDs,
+	}
+
+	resp, err := c.Post(url, payload)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("CreateIssueRelation 失败: %s, body=%s", resp.Status, string(resp.Body))
+	}
+	return resp.Body, nil
+}
+
+// RemoveIssueRelation 移除工作项关联
+func RemoveIssueRelation(c *plane.PlaneClient, slug, projectID, issueID, relationType, relatedID string) error {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/relations/%s/%s/", slug, projectID, issueID, relationType, relatedID)
+
+	resp, err := c.Delete(url)
+	if err != nil {
+		return err
+	}
+	if resp.StatusCode != 204 {
+		return fmt.Errorf("RemoveIssueRelation 失败: %s", resp.Status)
+	}
+	return nil
+}

+ 96 - 0
service/repositories/api/plane_v2/plane_v2_api/state.go

@@ -0,0 +1,96 @@
+package plane_v2_api
+
+import (
+	"fmt"
+
+	plane "fwh-toolbox-server/repositories/api/plane_v2"
+)
+
+// ListStates 列出项目状态
+func ListStates(c *plane.PlaneClient, slug, projectID string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/states/", slug, projectID)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("ListStates 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// CreateState 创建状态
+func CreateState(c *plane.PlaneClient, slug, projectID, name, color, group string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/states/", slug, projectID)
+	payload := map[string]interface{}{
+		"name":  name,
+		"color": color,
+		"group": group,
+	}
+
+	resp, err := c.Post(url, payload)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("CreateState 失败: %s, body=%s", resp.Status, string(resp.Body))
+	}
+	return resp.Body, nil
+}
+
+// GetState 获取状态详情
+func GetState(c *plane.PlaneClient, slug, projectID, stateID string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/states/%s/", slug, projectID, stateID)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("GetState 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// UpdateState 更新状态
+func UpdateState(c *plane.PlaneClient, slug, projectID, stateID string, data map[string]interface{}) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/states/%s/", slug, projectID, stateID)
+
+	resp, err := c.Patch(url, data)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("UpdateState 失败: %s, body=%s", resp.Status, string(resp.Body))
+	}
+	return resp.Body, nil
+}
+
+// DeleteState 删除状态
+func DeleteState(c *plane.PlaneClient, slug, projectID, stateID string) error {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/states/%s/", slug, projectID, stateID)
+
+	resp, err := c.Delete(url)
+	if err != nil {
+		return err
+	}
+	if resp.StatusCode != 204 {
+		return fmt.Errorf("DeleteState 失败: %s", resp.Status)
+	}
+	return nil
+}
+
+// MarkStateDefault 标记状态为默认
+func MarkStateDefault(c *plane.PlaneClient, slug, projectID, stateID string) error {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/states/%s/default/", slug, projectID, stateID)
+
+	resp, err := c.Post(url, nil)
+	if err != nil {
+		return err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return fmt.Errorf("MarkStateDefault 失败: %s", resp.Status)
+	}
+	return nil
+}

+ 77 - 0
service/repositories/api/plane_v2/plane_v2_api/sticky.go

@@ -0,0 +1,77 @@
+package plane_v2_api
+
+import (
+	"fmt"
+
+	plane "fwh-toolbox-server/repositories/api/plane_v2"
+)
+
+// ListStickies 列出工作空间便签
+func ListStickies(c *plane.PlaneClient, slug string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/stickies/", slug)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("ListStickies 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// CreateSticky 创建便签
+func CreateSticky(c *plane.PlaneClient, slug string, data map[string]interface{}) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/stickies/", slug)
+
+	resp, err := c.Post(url, data)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("CreateSticky 失败: %s, body=%s", resp.Status, string(resp.Body))
+	}
+	return resp.Body, nil
+}
+
+// GetSticky 获取便签详情
+func GetSticky(c *plane.PlaneClient, slug, stickyID string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/stickies/%s/", slug, stickyID)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("GetSticky 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// UpdateSticky 更新便签
+func UpdateSticky(c *plane.PlaneClient, slug, stickyID string, data map[string]interface{}) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/stickies/%s/", slug, stickyID)
+
+	resp, err := c.Patch(url, data)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("UpdateSticky 失败: %s, body=%s", resp.Status, string(resp.Body))
+	}
+	return resp.Body, nil
+}
+
+// DeleteSticky 删除便签
+func DeleteSticky(c *plane.PlaneClient, slug, stickyID string) error {
+	url := fmt.Sprintf("/api/workspaces/%s/stickies/%s/", slug, stickyID)
+
+	resp, err := c.Delete(url)
+	if err != nil {
+		return err
+	}
+	if resp.StatusCode != 204 {
+		return fmt.Errorf("DeleteSticky 失败: %s", resp.Status)
+	}
+	return nil
+}

+ 78 - 0
service/repositories/api/plane_v2/plane_v2_api/workspace.go

@@ -0,0 +1,78 @@
+package plane_v2_api
+
+import (
+	"fmt"
+
+	plane "fwh-toolbox-server/repositories/api/plane_v2"
+)
+
+// ListWorkspaces 列出所有工作空间
+func ListWorkspaces(c *plane.PlaneClient) ([]byte, error) {
+	resp, err := c.Get("/api/workspaces/")
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("ListWorkspaces 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// CreateWorkspace 创建工作空间
+func CreateWorkspace(c *plane.PlaneClient, name, slug string) ([]byte, error) {
+	payload := map[string]interface{}{
+		"name": name,
+		"slug": slug,
+	}
+
+	resp, err := c.Post("/api/workspaces/", payload)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("CreateWorkspace 失败: %s, body=%s", resp.Status, string(resp.Body))
+	}
+	return resp.Body, nil
+}
+
+// GetWorkspace 获取工作空间详情
+func GetWorkspace(c *plane.PlaneClient, slug string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/", slug)
+
+	resp, err := c.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("GetWorkspace 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// UpdateWorkspace 更新工作空间
+func UpdateWorkspace(c *plane.PlaneClient, slug string, data map[string]interface{}) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/", slug)
+
+	resp, err := c.Patch(url, data)
+	if err != nil {
+		return nil, err
+	}
+	if !plane.IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("UpdateWorkspace 失败: %s, body=%s", resp.Status, string(resp.Body))
+	}
+	return resp.Body, nil
+}
+
+// DeleteWorkspace 删除工作空间
+func DeleteWorkspace(c *plane.PlaneClient, slug string) error {
+	url := fmt.Sprintf("/api/workspaces/%s/", slug)
+
+	resp, err := c.Delete(url)
+	if err != nil {
+		return err
+	}
+	if resp.StatusCode != 204 {
+		return fmt.Errorf("DeleteWorkspace 失败: %s", resp.Status)
+	}
+	return nil
+}

+ 0 - 128
service/repositories/api/plane_v2/plane_workspace.go

@@ -1,128 +0,0 @@
-package plane_v2
-
-import (
-	"encoding/json"
-	"fmt"
-
-	"go.uber.org/zap"
-)
-
-// ==================== Workspace API ====================
-
-// ListWorkspaces 列出当前用户的工作空间
-func ListWorkspaces() ([]byte, error) {
-	resp, err := planeWebGet("/api/workspaces/")
-	if err != nil {
-		zap.L().Error("[PlaneWeb] ListWorkspaces 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] ListWorkspaces 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("ListWorkspaces 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// CreateWorkspace 创建工作空间
-func CreateWorkspace(name, slug string) ([]byte, error) {
-	payload := map[string]interface{}{
-		"name": name,
-		"slug": slug,
-	}
-
-	resp, err := planeWebPost("/api/workspaces/", payload)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] CreateWorkspace 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if resp.StatusCode != 201 {
-		zap.L().Error("[PlaneWeb] CreateWorkspace 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("CreateWorkspace 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// GetWorkspace 获取工作空间详情
-func GetWorkspace(slug string) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/", slug)
-
-	resp, err := planeWebGet(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] GetWorkspace 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] GetWorkspace 返回错误", zap.String("status", resp.Status))
-		return nil, fmt.Errorf("GetWorkspace 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// UpdateWorkspace 更新工作空间
-func UpdateWorkspace(slug string, data map[string]interface{}) ([]byte, error) {
-	url := fmt.Sprintf("/api/workspaces/%s/", slug)
-
-	resp, err := planeWebPatch(url, data)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] UpdateWorkspace 请求失败", zap.Error(err))
-		return nil, err
-	}
-
-	if !isWebSuccess(resp.StatusCode) {
-		zap.L().Error("[PlaneWeb] UpdateWorkspace 返回错误",
-			zap.String("status", resp.Status),
-			zap.String("body", string(resp.Body)),
-		)
-		return nil, fmt.Errorf("UpdateWorkspace 失败: %s", resp.Status)
-	}
-
-	return resp.Body, nil
-}
-
-// DeleteWorkspace 删除工作空间
-func DeleteWorkspace(slug string) error {
-	url := fmt.Sprintf("/api/workspaces/%s/", slug)
-
-	resp, err := planeWebDelete(url)
-	if err != nil {
-		zap.L().Error("[PlaneWeb] DeleteWorkspace 请求失败", zap.Error(err))
-		return err
-	}
-
-	if resp.StatusCode != 204 {
-		zap.L().Error("[PlaneWeb] DeleteWorkspace 返回错误", zap.String("status", resp.Status))
-		return fmt.Errorf("DeleteWorkspace 失败: %s", resp.Status)
-	}
-
-	return nil
-}
-
-// GetCurrentWorkspace 获取当前配置的工作空间详情(使用配置中的slug)
-func GetCurrentWorkspace() ([]byte, error) {
-	return GetWorkspace(webWorkspaceSlug)
-}
-
-// WorkspaceInfo 工作空间基础信息
-type WorkspaceInfo struct {
-	ID   string `json:"id"`
-	Name string `json:"name"`
-	Slug string `json:"slug"`
-}
-
-// ParseWorkspaceList 解析工作空间列表
-func ParseWorkspaceList(data []byte) ([]WorkspaceInfo, error) {
-	var workspaces []WorkspaceInfo
-	if err := json.Unmarshal(data, &workspaces); err != nil {
-		return nil, fmt.Errorf("解析工作空间列表失败: %w", err)
-	}
-	return workspaces, nil
-}

+ 180 - 0
service/repositories/api/plane_v2/service.go

@@ -0,0 +1,180 @@
+package plane_v2
+
+import (
+	"encoding/json"
+	"fmt"
+	"fwh-toolbox-server/config"
+)
+
+// PlaneService 便捷封装(绑定固定的 workspace/project,共享全局 PlaneClient)
+type PlaneService struct {
+	Client          *PlaneClient
+	WorkspaceSlug   string
+	ProjectId       string
+	LabelBugId      string
+	LabelFeatureId  string
+	LabelFeedbackId string
+}
+
+// NewPlaneService 创建 PlaneService(须先调用 InitPlaneClient)
+func NewPlaneService(cfg *config.PlaneWebApiConfig) *PlaneService {
+	if globalClient == nil {
+		panic("[PlaneService] PlaneClient 未初始化,请先调用 InitPlaneClient")
+	}
+
+	return &PlaneService{
+		Client:          globalClient,
+		WorkspaceSlug:   cfg.WorkspaceSlug,
+		ProjectId:       cfg.ProjectId,
+		LabelBugId:      cfg.LabelBugId,
+		LabelFeatureId:  cfg.LabelFeatureId,
+		LabelFeedbackId: cfg.LabelFeedbackId,
+	}
+}
+
+// ============ Intake 便捷方法 ============
+
+func (s *PlaneService) CreateIntakeIssue(issue IntakeIssueRequest) (*IntakeIssueResponse, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/intake-issues/", s.WorkspaceSlug, s.ProjectId)
+	payload := map[string]any{"source": "IN_APP", "issue": issue}
+
+	resp, err := s.Client.Post(url, payload)
+	if err != nil {
+		return nil, err
+	}
+	if !IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("CreateIntakeIssue 失败: %s", resp.Status)
+	}
+	var result IntakeIssueResponse
+	if err := json.Unmarshal(resp.Body, &result); err != nil {
+		return nil, err
+	}
+	return &result, nil
+}
+
+func (s *PlaneService) ListIntakeIssues(status string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/intake-issues/", s.WorkspaceSlug, s.ProjectId)
+	if status != "" {
+		url += "?status=" + status
+	}
+	resp, err := s.Client.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("ListIntakeIssues 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+func (s *PlaneService) GetIntakeIssue(issueID string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/intake-issues/%s/?expand=issue_inbox", s.WorkspaceSlug, s.ProjectId, issueID)
+	resp, err := s.Client.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("GetIntakeIssue 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+// ============ Asset 便捷方法 ============
+
+func (s *PlaneService) UploadProjectAsset(fileName, mimeType string, fileData []byte, entityType string) (*AssetUploadResult, error) {
+	// Step 1: 创建资源
+	createURL := fmt.Sprintf("/api/assets/v2/workspaces/%s/projects/%s/", s.WorkspaceSlug, s.ProjectId)
+	createPayload := map[string]any{
+		"name": fileName, "type": mimeType, "size": len(fileData),
+		"entity_type": entityType, "entity_identifier": "",
+	}
+	resp, err := s.Client.Post(createURL, createPayload)
+	if err != nil {
+		return nil, err
+	}
+	if !IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("CreateProjectAsset 失败: %s", resp.Status)
+	}
+	var result AssetUploadResult
+	if err := json.Unmarshal(resp.Body, &result); err != nil {
+		return nil, err
+	}
+
+	// Step 2: 上传到 S3
+	if err := s.Client.PostS3(result.UploadData.URL, result.UploadData.Fields, fileData, fileName); err != nil {
+		return nil, err
+	}
+
+	// Step 3: 确认
+	confirmURL := fmt.Sprintf("/api/assets/v2/workspaces/%s/projects/%s/%s/", s.WorkspaceSlug, s.ProjectId, result.AssetID)
+	s.Client.Patch(confirmURL, map[string]any{})
+
+	return &result, nil
+}
+
+func (s *PlaneService) GetProjectAssetURL(assetID string) (string, error) {
+	url := fmt.Sprintf("/api/assets/v2/workspaces/%s/projects/%s/%s/", s.WorkspaceSlug, s.ProjectId, assetID)
+	return s.Client.GetRedirectLocation(url)
+}
+
+func (s *PlaneService) DeleteProjectAsset(assetID string) error {
+	url := fmt.Sprintf("/api/assets/v2/workspaces/%s/projects/%s/%s/", s.WorkspaceSlug, s.ProjectId, assetID)
+	resp, err := s.Client.Delete(url)
+	if err != nil {
+		return err
+	}
+	if resp.StatusCode != 204 {
+		return fmt.Errorf("DeleteProjectAsset 失败: %s", resp.Status)
+	}
+	return nil
+}
+
+func (s *PlaneService) BindAssetsToEntity(entityID string, assetIDs []string) error {
+	url := fmt.Sprintf("/api/assets/v2/workspaces/%s/projects/%s/%s/bulk/", s.WorkspaceSlug, s.ProjectId, entityID)
+	resp, err := s.Client.Post(url, map[string]any{"asset_ids": assetIDs})
+	if err != nil {
+		return err
+	}
+	if resp.StatusCode != 204 && !IsSuccess(resp.StatusCode) {
+		return fmt.Errorf("BindAssetsToEntity 失败: %s", resp.Status)
+	}
+	return nil
+}
+
+// ============ Page 便捷方法 ============
+
+func (s *PlaneService) ListPages() ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/", s.WorkspaceSlug, s.ProjectId)
+	resp, err := s.Client.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("ListPages 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+func (s *PlaneService) GetPage(pageID string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/%s/", s.WorkspaceSlug, s.ProjectId, pageID)
+	resp, err := s.Client.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("GetPage 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}
+
+func (s *PlaneService) GetPageDescription(pageID string) ([]byte, error) {
+	url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/%s/description/", s.WorkspaceSlug, s.ProjectId, pageID)
+	resp, err := s.Client.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if !IsSuccess(resp.StatusCode) {
+		return nil, fmt.Errorf("GetPageDescription 失败: %s", resp.Status)
+	}
+	return resp.Body, nil
+}

+ 69 - 0
service/repositories/api/plane_v2/types.go

@@ -0,0 +1,69 @@
+package plane_v2
+
+// ============ Intake 类型 ============
+
+// WebIntakeIssueRequest 创建 Intake 工作项请求
+type WebIntakeIssueRequest struct {
+	Name            string   `json:"name"`
+	DescriptionHTML string   `json:"description_html,omitempty"`
+	Priority        string   `json:"priority,omitempty"`
+	StateID         string   `json:"state_id,omitempty"`
+	AssigneeIDs     []string `json:"assignee_ids,omitempty"`
+	LabelIDs        []string `json:"label_ids,omitempty"`
+	StartDate       string   `json:"start_date,omitempty"`
+	TargetDate      *string  `json:"target_date"`
+	ParentID        string   `json:"parent_id,omitempty"`
+}
+
+// WebIntakeIssueResponse Intake 创建响应
+type WebIntakeIssueResponse struct {
+	Issue struct {
+		ID              string `json:"id"`
+		Name            string `json:"name"`
+		DescriptionHTML string `json:"description_html"`
+		Priority        string `json:"priority"`
+	} `json:"issue"`
+	Status    int    `json:"status"`
+	CreatedAt string `json:"created_at"`
+}
+
+// 类型别名(供 plane_v2_api 和 service.go 使用)
+type IntakeIssueRequest = WebIntakeIssueRequest
+type IntakeIssueResponse = WebIntakeIssueResponse
+
+// ============ Asset 类型 ============
+
+// WebAssetUploadResult 资源上传结果
+type WebAssetUploadResult struct {
+	UploadData struct {
+		URL    string            `json:"url"`
+		Fields map[string]string `json:"fields"`
+	} `json:"upload_data"`
+	AssetID  string `json:"asset_id"`
+	AssetURL string `json:"asset_url"`
+}
+
+type AssetUploadResult = WebAssetUploadResult
+
+// ============ Entity Type 常量 ============
+
+const (
+	EntityIssueDescription      = "ISSUE_DESCRIPTION"
+	EntityIssueAttachment       = "ISSUE_ATTACHMENT"
+	EntityCommentDescription    = "COMMENT_DESCRIPTION"
+	EntityPageDescription       = "PAGE_DESCRIPTION"
+	EntityDraftIssueDescription = "DRAFT_ISSUE_DESCRIPTION"
+	EntityProjectCover          = "PROJECT_COVER"
+	EntityUserAvatar            = "USER_AVATAR"
+	EntityUserCover             = "USER_COVER"
+	EntityWorkspaceLogo         = "WORKSPACE_LOGO"
+)
+
+// ============ Workspace 类型 ============
+
+// WorkspaceInfo 工作空间信息
+type WorkspaceInfo struct {
+	ID   string `json:"id"`
+	Name string `json:"name"`
+	Slug string `json:"slug"`
+}

+ 14 - 169
service/repositories/repositories.go

@@ -1,6 +1,7 @@
 package repositories
 
 import (
+	"fwh-toolbox-server/config"
 	"fwh-toolbox-server/domain"
 	"fwh-toolbox-server/repositories/api/acceleronix"
 	"fwh-toolbox-server/repositories/api/fwh_pro"
@@ -15,6 +16,17 @@ import (
 	"fwh-toolbox-server/repositories/redis"
 )
 
+// ============ Plane V2 新架构(PlaneService)============
+
+// Plane 全局 PlaneService 实例(新架构入口)
+var Plane *plane_v2.PlaneService
+
+// InitPlane 初始化新架构 Plane(启动时调用)
+func InitPlane(cfg *config.PlaneWebApiConfig) {
+	plane_v2.InitPlaneClient(cfg)
+	Plane = plane_v2.NewPlaneService(cfg)
+}
+
 // 聚合包
 
 /*************** API ***************/
@@ -218,179 +230,12 @@ var PlaneUpdateSticky = plane_v1.UpdateSticky   // 更新便签
 var PlaneDeleteSticky = plane_v1.DeleteSticky   // 删除便签
 
 // Plane_V2 Web API (Session + CSRF 认证)
+
+// Plane V2 类型导出(兼容旧调用)
 type PlaneWebIntakeIssueRequest = plane_v2.WebIntakeIssueRequest
 type PlaneWebIntakeIssueResponse = plane_v2.WebIntakeIssueResponse
 type PlaneWebAssetUploadResult = plane_v2.WebAssetUploadResult
-type PlaneWebWorkspaceInfo = plane_v2.WorkspaceInfo
-
-var PlaneWebHttpInit = plane_v2.PlaneWebHttpInit             // 初始化Plane Web HTTP客户端
-var PlaneWebGetWorkspaceSlug = plane_v2.GetWorkspaceSlug     // 获取WorkspaceSlug
-var PlaneWebGetProjectId = plane_v2.GetProjectId             // 获取ProjectId
-var PlaneWebGetLabelBugId = plane_v2.GetWebLabelBugId        // 获取Bug标签ID
-var PlaneWebGetLabelFeatureId = plane_v2.GetWebLabelFeatureId // 获取Feature标签ID
-var PlaneWebGetLabelFeedbackId = plane_v2.GetWebLabelFeedbackId // 获取Feedback标签ID
-
-// Plane_V2 - Workspace
-var PlaneWebListWorkspaces = plane_v2.ListWorkspaces         // 列出工作空间
-var PlaneWebCreateWorkspace = plane_v2.CreateWorkspace       // 创建工作空间
-var PlaneWebGetWorkspace = plane_v2.GetWorkspace             // 获取工作空间详情
-var PlaneWebUpdateWorkspace = plane_v2.UpdateWorkspace       // 更新工作空间
-var PlaneWebDeleteWorkspace = plane_v2.DeleteWorkspace       // 删除工作空间
-
-// Plane_V2 - Project
-var PlaneWebListProjects = plane_v2.ListProjects             // 列出项目
-var PlaneWebCreateProject = plane_v2.CreateProject           // 创建项目
-var PlaneWebGetProject = plane_v2.GetProject                 // 获取项目详情
-var PlaneWebUpdateProject = plane_v2.UpdateProject           // 更新项目
-var PlaneWebDeleteProject = plane_v2.DeleteProject           // 删除项目
-var PlaneWebArchiveProject = plane_v2.ArchiveProject         // 归档项目
-var PlaneWebUnarchiveProject = plane_v2.UnarchiveProject     // 取消归档项目
-
-// Plane_V2 - Issue
-var PlaneWebListIssues = plane_v2.ListIssues                 // 列出工作项
-var PlaneWebCreateIssue = plane_v2.CreateIssue               // 创建工作项
-var PlaneWebGetIssue = plane_v2.GetIssue                     // 获取工作项详情
-var PlaneWebUpdateIssue = plane_v2.UpdateIssue               // 更新工作项
-var PlaneWebDeleteIssue = plane_v2.DeleteIssue               // 删除工作项
-var PlaneWebGetSubIssues = plane_v2.GetSubIssues             // 获取子工作项
-var PlaneWebArchiveIssue = plane_v2.ArchiveIssue             // 归档工作项
-var PlaneWebUnarchiveIssue = plane_v2.UnarchiveIssue         // 取消归档工作项
-var PlaneWebGetIssueActivity = plane_v2.GetIssueActivity     // 获取工作项活动历史
-
-// Plane_V2 - Comment
-var PlaneWebListComments = plane_v2.ListComments             // 列出评论
-var PlaneWebCreateComment = plane_v2.CreateComment           // 创建评论
-var PlaneWebGetComment = plane_v2.GetComment                 // 获取评论详情
-var PlaneWebUpdateComment = plane_v2.UpdateComment           // 更新评论
-var PlaneWebDeleteComment = plane_v2.DeleteComment           // 删除评论
-
-// Plane_V2 - Link
-var PlaneWebListIssueLinks = plane_v2.ListIssueLinks         // 列出工作项链接
-var PlaneWebCreateIssueLink = plane_v2.CreateIssueLink       // 创建工作项链接
-var PlaneWebUpdateIssueLink = plane_v2.UpdateIssueLink       // 更新工作项链接
-var PlaneWebDeleteIssueLink = plane_v2.DeleteIssueLink       // 删除工作项链接
-
-// Plane_V2 - Relation
-var PlaneWebListIssueRelations = plane_v2.ListIssueRelations       // 列出工作项关系
-var PlaneWebCreateIssueRelation = plane_v2.CreateIssueRelation     // 创建工作项关系
-var PlaneWebRemoveIssueRelation = plane_v2.RemoveIssueRelation     // 移除工作项关系
-
-// Plane_V2 - Cycle
-var PlaneWebListCycles = plane_v2.ListCycles                       // 列出迭代
-var PlaneWebCreateCycle = plane_v2.CreateCycle                     // 创建迭代
-var PlaneWebGetCycle = plane_v2.GetCycle                           // 获取迭代详情
-var PlaneWebUpdateCycle = plane_v2.UpdateCycle                     // 更新迭代
-var PlaneWebDeleteCycle = plane_v2.DeleteCycle                     // 删除迭代
-var PlaneWebListCycleIssues = plane_v2.ListCycleIssues             // 列出迭代工作项
-var PlaneWebAddIssuesToCycle = plane_v2.AddIssuesToCycle           // 添加工作项到迭代
-var PlaneWebRemoveIssueFromCycle = plane_v2.RemoveIssueFromCycle   // 从迭代移除工作项
-var PlaneWebTransferCycleIssues = plane_v2.TransferCycleIssues     // 转移迭代工作项
-var PlaneWebArchiveCycle = plane_v2.ArchiveCycle                   // 归档迭代
-var PlaneWebListArchivedCycles = plane_v2.ListArchivedCycles       // 列出已归档迭代
-var PlaneWebUnarchiveCycle = plane_v2.UnarchiveCycle               // 取消归档迭代
-var PlaneWebGetCycleProgress = plane_v2.GetCycleProgress           // 获取迭代进度
-var PlaneWebGetCycleAnalytics = plane_v2.GetCycleAnalytics         // 获取迭代分析
-
-// Plane_V2 - Module
-var PlaneWebListModules = plane_v2.ListModules                     // 列出模块
-var PlaneWebCreateModule = plane_v2.CreateModule                   // 创建模块
-var PlaneWebGetModule = plane_v2.GetModule                         // 获取模块详情
-var PlaneWebUpdateModule = plane_v2.UpdateModule                   // 更新模块
-var PlaneWebDeleteModule = plane_v2.DeleteModule                   // 删除模块
-var PlaneWebListModuleIssues = plane_v2.ListModuleIssues           // 列出模块工作项
-var PlaneWebAddIssuesToModule = plane_v2.AddIssuesToModule         // 添加工作项到模块
-var PlaneWebRemoveIssueFromModule = plane_v2.RemoveIssueFromModule // 从模块移除工作项
-var PlaneWebListModuleLinks = plane_v2.ListModuleLinks             // 列出模块链接
-var PlaneWebCreateModuleLink = plane_v2.CreateModuleLink           // 创建模块链接
-var PlaneWebUpdateModuleLink = plane_v2.UpdateModuleLink           // 更新模块链接
-var PlaneWebDeleteModuleLink = plane_v2.DeleteModuleLink           // 删除模块链接
-var PlaneWebArchiveModule = plane_v2.ArchiveModule                 // 归档模块
-var PlaneWebListArchivedModules = plane_v2.ListArchivedModules     // 列出已归档模块
-var PlaneWebUnarchiveModule = plane_v2.UnarchiveModule             // 取消归档模块
-
-// Plane_V2 - State
-var PlaneWebListStates = plane_v2.ListStates                       // 列出状态
-var PlaneWebCreateState = plane_v2.CreateState                     // 创建状态
-var PlaneWebGetState = plane_v2.GetState                           // 获取状态详情
-var PlaneWebUpdateState = plane_v2.UpdateState                     // 更新状态
-var PlaneWebDeleteState = plane_v2.DeleteState                     // 删除状态
-var PlaneWebMarkStateDefault = plane_v2.MarkStateDefault           // 设为默认状态
-
-// Plane_V2 - Label
-var PlaneWebListLabels = plane_v2.ListLabels                       // 列出标签
-var PlaneWebCreateLabel = plane_v2.CreateLabel                     // 创建标签
-var PlaneWebGetLabel = plane_v2.GetLabel                           // 获取标签详情
-var PlaneWebUpdateLabel = plane_v2.UpdateLabel                     // 更新标签
-var PlaneWebDeleteLabel = plane_v2.DeleteLabel                     // 删除标签
-
-// Plane_V2 - Member
-var PlaneWebListWorkspaceMembers = plane_v2.ListWorkspaceMembers         // 列出工作空间成员
-var PlaneWebGetWorkspaceMember = plane_v2.GetWorkspaceMember             // 获取工作空间成员详情
-var PlaneWebUpdateWorkspaceMemberRole = plane_v2.UpdateWorkspaceMemberRole // 更新工作空间成员角色
-var PlaneWebRemoveWorkspaceMember = plane_v2.RemoveWorkspaceMember       // 移除工作空间成员
-var PlaneWebListProjectMembers = plane_v2.ListProjectMembers             // 列出项目成员
-var PlaneWebAddProjectMember = plane_v2.AddProjectMember                 // 添加项目成员
-var PlaneWebGetProjectMember = plane_v2.GetProjectMember                 // 获取项目成员详情
-var PlaneWebUpdateProjectMemberRole = plane_v2.UpdateProjectMemberRole   // 更新项目成员角色
-var PlaneWebRemoveProjectMember = plane_v2.RemoveProjectMember           // 移除项目成员
-var PlaneWebGetCurrentUser = plane_v2.GetCurrentUser                     // 获取当前用户信息
-var PlaneWebUpdateCurrentUser = plane_v2.UpdateCurrentUser               // 更新当前用户信息
-
-// Plane_V2 - Intake
-var PlaneWebListIntakeIssues = plane_v2.ListIntakeIssues             // 列出收件箱工作项
-var PlaneWebCreateIntakeIssue = plane_v2.CreateIntakeIssue           // 创建收件箱工作项
-var PlaneWebCreateIntakeIssueRaw = plane_v2.CreateIntakeIssueRaw     // 创建收件箱工作项(Raw)
-var PlaneWebGetIntakeIssue = plane_v2.GetIntakeIssue                 // 获取收件箱工作项详情
-var PlaneWebUpdateIntakeIssue = plane_v2.UpdateIntakeIssue           // 更新收件箱工作项
-var PlaneWebAcceptIntakeIssue = plane_v2.AcceptIntakeIssue           // 接受收件箱工作项
-var PlaneWebRejectIntakeIssue = plane_v2.RejectIntakeIssue           // 拒绝收件箱工作项
-var PlaneWebSnoozeIntakeIssue = plane_v2.SnoozeIntakeIssue           // 暂缓收件箱工作项
-var PlaneWebDuplicateIntakeIssue = plane_v2.DuplicateIntakeIssue     // 标记重复收件箱工作项
-var PlaneWebDeleteIntakeIssue = plane_v2.DeleteIntakeIssue           // 删除收件箱工作项
-var PlaneWebListCurrentIntakeIssues = plane_v2.ListCurrentIntakeIssues   // 列出当前项目收件箱工作项
-var PlaneWebCreateCurrentIntakeIssue = plane_v2.CreateCurrentIntakeIssue // 在当前项目创建收件箱工作项
-
-// Plane_V2 - Asset
 const PlaneWebEntityIssueDescription = plane_v2.EntityIssueDescription
-const PlaneWebEntityIssueAttachment = plane_v2.EntityIssueAttachment
-const PlaneWebEntityCommentDescription = plane_v2.EntityCommentDescription
-const PlaneWebEntityPageDescription = plane_v2.EntityPageDescription
-
-var PlaneWebCreateProjectAsset = plane_v2.CreateProjectAsset             // 创建项目资源
-var PlaneWebUploadToS3 = plane_v2.UploadToS3                             // 上传文件到S3
-var PlaneWebConfirmProjectAssetUpload = plane_v2.ConfirmProjectAssetUpload // 确认项目资源上传
-var PlaneWebGetProjectAsset = plane_v2.GetProjectAsset                   // 下载项目资源
-var PlaneWebGetProjectAssetURL = plane_v2.GetProjectAssetURL             // 获取项目资源预签名URL
-var PlaneWebDeleteProjectAsset = plane_v2.DeleteProjectAsset             // 删除项目资源
-var PlaneWebBindAssetsToEntity = plane_v2.BindAssetsToEntity             // 批量绑定资源到实体
-var PlaneWebCreateWorkspaceAsset = plane_v2.CreateWorkspaceAsset         // 创建工作空间资源
-var PlaneWebConfirmWorkspaceAssetUpload = plane_v2.ConfirmWorkspaceAssetUpload // 确认工作空间资源上传
-var PlaneWebCreateUserAsset = plane_v2.CreateUserAsset                   // 创建用户资源
-var PlaneWebConfirmUserAssetUpload = plane_v2.ConfirmUserAssetUpload     // 确认用户资源上传
-var PlaneWebUploadProjectAsset = plane_v2.UploadProjectAsset             // 完整上传项目资源流程
-var PlaneWebUploadCurrentProjectAsset = plane_v2.UploadCurrentProjectAsset // 在当前项目上传资源
-
-// Plane_V2 - Page
-var PlaneWebListPages = plane_v2.ListPages                     // 列出页面
-var PlaneWebCreatePage = plane_v2.CreatePage                   // 创建页面
-var PlaneWebGetPage = plane_v2.GetPage                         // 获取页面详情
-var PlaneWebUpdatePage = plane_v2.UpdatePage                   // 更新页面
-var PlaneWebGetPageDescription = plane_v2.GetPageDescription   // 获取页面描述
-var PlaneWebUpdatePageDescription = plane_v2.UpdatePageDescription // 更新页面描述
-var PlaneWebDeletePage = plane_v2.DeletePage                   // 删除页面
-var PlaneWebArchivePage = plane_v2.ArchivePage                 // 归档页面
-var PlaneWebUnarchivePage = plane_v2.UnarchivePage             // 取消归档页面
-var PlaneWebLockPage = plane_v2.LockPage                       // 锁定页面
-var PlaneWebUnlockPage = plane_v2.UnlockPage                   // 解锁页面
-var PlaneWebSetPageAccess = plane_v2.SetPageAccess             // 设置页面访问权限
-var PlaneWebDuplicatePage = plane_v2.DuplicatePage             // 复制页面
-
-// Plane_V2 - Sticky
-var PlaneWebListStickies = plane_v2.ListStickies               // 列出便签
-var PlaneWebCreateSticky = plane_v2.CreateSticky               // 创建便签
-var PlaneWebGetSticky = plane_v2.GetSticky                     // 获取便签详情
-var PlaneWebUpdateSticky = plane_v2.UpdateSticky               // 更新便签
-var PlaneWebDeleteSticky = plane_v2.DeleteSticky               // 删除便签
 
 /*************** EVENTS ***************/
 // Xlog

+ 14 - 24
service/services/feedbackServices.go

@@ -43,13 +43,13 @@ func SubmitFeedback(body dto.FeedbackSubmitDTO, userName string) (*repositories.
 
 	// 选择 Label(始终添加 Feedback 标签 + 类型标签)
 	var labelIds []string
-	if repositories.PlaneWebGetLabelFeedbackId() != "" {
-		labelIds = append(labelIds, repositories.PlaneWebGetLabelFeedbackId())
+	if repositories.Plane.LabelFeedbackId != "" {
+		labelIds = append(labelIds, repositories.Plane.LabelFeedbackId)
 	}
-	if body.Type == "bug" && repositories.PlaneWebGetLabelBugId() != "" {
-		labelIds = append(labelIds, repositories.PlaneWebGetLabelBugId())
-	} else if body.Type == "feature" && repositories.PlaneWebGetLabelFeatureId() != "" {
-		labelIds = append(labelIds, repositories.PlaneWebGetLabelFeatureId())
+	if body.Type == "bug" && repositories.Plane.LabelBugId != "" {
+		labelIds = append(labelIds, repositories.Plane.LabelBugId)
+	} else if body.Type == "feature" && repositories.Plane.LabelFeatureId != "" {
+		labelIds = append(labelIds, repositories.Plane.LabelFeatureId)
 	}
 
 	// 映射优先级
@@ -64,7 +64,7 @@ func SubmitFeedback(body dto.FeedbackSubmitDTO, userName string) (*repositories.
 	}
 
 	// 调用 Plane V2 Web Intake API 创建Issue
-	result, err := repositories.PlaneWebCreateCurrentIntakeIssue(req)
+	result, err := repositories.Plane.CreateIntakeIssue(req)
 	if err != nil {
 		return nil, err
 	}
@@ -73,10 +73,8 @@ func SubmitFeedback(body dto.FeedbackSubmitDTO, userName string) (*repositories.
 
 	// 创建成功后,如果有附件,将 Asset 绑定到 Issue Entity
 	if len(body.AssetIds) > 0 {
-		slug := repositories.PlaneWebGetWorkspaceSlug()
-		projectId := repositories.PlaneWebGetProjectId()
 
-		if err := repositories.PlaneWebBindAssetsToEntity(slug, projectId, issueId, body.AssetIds); err != nil {
+		if err := repositories.Plane.BindAssetsToEntity(issueId, body.AssetIds); err != nil {
 			// 绑定失败不阻塞整体提交,但记录警告
 			zap.L().Warn("[Feedback] 绑定附件到Issue失败",
 				zap.String("issueId", issueId),
@@ -158,12 +156,12 @@ func buildDescriptionHTML(body dto.FeedbackSubmitDTO, userName string) string {
 // 只返回带有 Feedback 标签的记录,包含所有状态(Pending/Accepted/Rejected/Snoozed/Duplicate)
 func GetFeedbackHistory(userName string) ([]FeedbackHistoryItem, error) {
 	// 查询所有状态的 Intake 记录:-2 Pending, -1 Rejected, 0 Snoozed, 1 Accepted, 2 Duplicate
-	data, err := repositories.PlaneWebListCurrentIntakeIssues("-2,-1,0,1,2")
+	data, err := repositories.Plane.ListIntakeIssues("-2,-1,0,1,2")
 	if err != nil {
 		return nil, err
 	}
 
-	feedbackLabelId := repositories.PlaneWebGetLabelFeedbackId()
+	feedbackLabelId := repositories.Plane.LabelFeedbackId
 
 	// V2 返回的是 raw JSON,直接解析
 	type intakeItem struct {
@@ -271,10 +269,8 @@ type FeedbackAttachment struct {
 
 // GetFeedbackDetail 获取单条反馈详情(含 description_html 和附件 ID 列表)
 func GetFeedbackDetail(issueId string) (*FeedbackDetailResult, error) {
-	slug := repositories.PlaneWebGetWorkspaceSlug()
-	projectId := repositories.PlaneWebGetProjectId()
 
-	data, err := repositories.PlaneWebGetIntakeIssue(slug, projectId, issueId)
+	data, err := repositories.Plane.GetIntakeIssue(issueId)
 	if err != nil {
 		return nil, err
 	}
@@ -350,16 +346,14 @@ func mapPriority(priority string) string {
 
 // UploadFeedbackAsset 通过 Plane V2 Assets API 上传文件
 func UploadFeedbackAsset(fileName string, contentType string, fileSize int64, fileData []byte) (*repositories.PlaneWebAssetUploadResult, error) {
-	return repositories.PlaneWebUploadCurrentProjectAsset(fileName, contentType, fileData, repositories.PlaneWebEntityIssueDescription)
+	return repositories.Plane.UploadProjectAsset(fileName, contentType, fileData, repositories.PlaneWebEntityIssueDescription)
 }
 
 // ProxyFeedbackAsset 代理获取附件文件(后端从 Plane 下载后返回给前端)
 func ProxyFeedbackAsset(assetId string) (string, []byte, error) {
-	slug := repositories.PlaneWebGetWorkspaceSlug()
-	projectId := repositories.PlaneWebGetProjectId()
 
 	// 获取预签名 URL
-	presignedURL, err := repositories.PlaneWebGetProjectAssetURL(slug, projectId, assetId)
+	presignedURL, err := repositories.Plane.GetProjectAssetURL(assetId)
 	if err != nil {
 		return "", nil, fmt.Errorf("获取附件URL失败: %w", err)
 	}
@@ -375,11 +369,7 @@ func ProxyFeedbackAsset(assetId string) (string, []byte, error) {
 
 // DeleteFeedbackAsset 删除已上传的附件 (V2)
 func DeleteFeedbackAsset(assetId string) error {
-	return repositories.PlaneWebDeleteProjectAsset(
-		repositories.PlaneWebGetWorkspaceSlug(),
-		repositories.PlaneWebGetProjectId(),
-		assetId,
-	)
+	return repositories.Plane.DeleteProjectAsset(assetId)
 }
 
 // httpGetResponse HTTP GET 响应