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) }