plane_intake.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. package plane_v2
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "go.uber.org/zap"
  6. )
  7. // ==================== Intake (收件箱) API ====================
  8. // WebIntakeIssueRequest 创建Intake工作项的请求结构
  9. type WebIntakeIssueRequest struct {
  10. Name string `json:"name"`
  11. DescriptionHTML string `json:"description_html,omitempty"`
  12. Priority string `json:"priority,omitempty"`
  13. StateID string `json:"state_id,omitempty"`
  14. AssigneeIDs []string `json:"assignee_ids,omitempty"`
  15. LabelIDs []string `json:"label_ids,omitempty"`
  16. StartDate string `json:"start_date,omitempty"`
  17. TargetDate *string `json:"target_date"`
  18. ParentID string `json:"parent_id,omitempty"`
  19. }
  20. // WebIntakeIssueResponse Intake创建响应
  21. type WebIntakeIssueResponse struct {
  22. Issue struct {
  23. ID string `json:"id"`
  24. Name string `json:"name"`
  25. DescriptionHTML string `json:"description_html"`
  26. Priority string `json:"priority"`
  27. } `json:"issue"`
  28. Status int `json:"status"`
  29. CreatedAt string `json:"created_at"`
  30. }
  31. // ListIntakeIssues 列出收件箱工作项
  32. // status: 逗号分隔的状态值,如 "-2,-1" (可为空表示不过滤)
  33. func ListIntakeIssues(slug, projectID, status string) ([]byte, error) {
  34. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/intake-issues/", slug, projectID)
  35. if status != "" {
  36. url += "?status=" + status
  37. }
  38. resp, err := planeWebGet(url)
  39. if err != nil {
  40. zap.L().Error("[PlaneWeb] ListIntakeIssues 请求失败", zap.Error(err))
  41. return nil, err
  42. }
  43. if !isWebSuccess(resp.StatusCode) {
  44. zap.L().Error("[PlaneWeb] ListIntakeIssues 返回错误", zap.String("status", resp.Status))
  45. return nil, fmt.Errorf("ListIntakeIssues 失败: %s", resp.Status)
  46. }
  47. return resp.Body, nil
  48. }
  49. // CreateIntakeIssue 创建收件箱工作项
  50. func CreateIntakeIssue(slug, projectID string, issue WebIntakeIssueRequest) (*WebIntakeIssueResponse, error) {
  51. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/intake-issues/", slug, projectID)
  52. payload := map[string]interface{}{
  53. "source": "IN_APP",
  54. "issue": issue,
  55. }
  56. resp, err := planeWebPost(url, payload)
  57. if err != nil {
  58. zap.L().Error("[PlaneWeb] CreateIntakeIssue 请求失败", zap.Error(err))
  59. return nil, err
  60. }
  61. if !isWebSuccess(resp.StatusCode) {
  62. zap.L().Error("[PlaneWeb] CreateIntakeIssue 返回错误",
  63. zap.String("status", resp.Status),
  64. zap.String("body", string(resp.Body)),
  65. )
  66. return nil, fmt.Errorf("CreateIntakeIssue 失败: %s", resp.Status)
  67. }
  68. var result WebIntakeIssueResponse
  69. if err := json.Unmarshal(resp.Body, &result); err != nil {
  70. zap.L().Error("[PlaneWeb] CreateIntakeIssue 响应解析失败", zap.Error(err))
  71. return nil, fmt.Errorf("响应解析失败: %w", err)
  72. }
  73. zap.L().Info("[PlaneWeb] CreateIntakeIssue 成功", zap.String("issueId", result.Issue.ID))
  74. return &result, nil
  75. }
  76. // CreateIntakeIssueRaw 创建收件箱工作项(原始map方式)
  77. func CreateIntakeIssueRaw(slug, projectID string, issueData map[string]interface{}) ([]byte, error) {
  78. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/intake-issues/", slug, projectID)
  79. payload := map[string]interface{}{
  80. "issue": issueData,
  81. }
  82. resp, err := planeWebPost(url, payload)
  83. if err != nil {
  84. zap.L().Error("[PlaneWeb] CreateIntakeIssueRaw 请求失败", zap.Error(err))
  85. return nil, err
  86. }
  87. if !isWebSuccess(resp.StatusCode) {
  88. zap.L().Error("[PlaneWeb] CreateIntakeIssueRaw 返回错误",
  89. zap.String("status", resp.Status),
  90. zap.String("body", string(resp.Body)),
  91. )
  92. return nil, fmt.Errorf("CreateIntakeIssueRaw 失败: %s", resp.Status)
  93. }
  94. return resp.Body, nil
  95. }
  96. // GetIntakeIssue 获取收件箱工作项详情
  97. func GetIntakeIssue(slug, projectID, issueID string) ([]byte, error) {
  98. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/intake-issues/%s/", slug, projectID, issueID)
  99. resp, err := planeWebGet(url)
  100. if err != nil {
  101. zap.L().Error("[PlaneWeb] GetIntakeIssue 请求失败", zap.Error(err))
  102. return nil, err
  103. }
  104. if !isWebSuccess(resp.StatusCode) {
  105. zap.L().Error("[PlaneWeb] GetIntakeIssue 返回错误", zap.String("status", resp.Status))
  106. return nil, fmt.Errorf("GetIntakeIssue 失败: %s", resp.Status)
  107. }
  108. return resp.Body, nil
  109. }
  110. // UpdateIntakeIssue 更新收件箱工作项(修改状态或内容)
  111. func UpdateIntakeIssue(slug, projectID, issueID string, data map[string]interface{}) ([]byte, error) {
  112. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/intake-issues/%s/", slug, projectID, issueID)
  113. resp, err := planeWebPatch(url, data)
  114. if err != nil {
  115. zap.L().Error("[PlaneWeb] UpdateIntakeIssue 请求失败", zap.Error(err))
  116. return nil, err
  117. }
  118. if !isWebSuccess(resp.StatusCode) {
  119. zap.L().Error("[PlaneWeb] UpdateIntakeIssue 返回错误",
  120. zap.String("status", resp.Status),
  121. zap.String("body", string(resp.Body)),
  122. )
  123. return nil, fmt.Errorf("UpdateIntakeIssue 失败: %s", resp.Status)
  124. }
  125. return resp.Body, nil
  126. }
  127. // AcceptIntakeIssue 接受收件箱工作项
  128. func AcceptIntakeIssue(slug, projectID, issueID string) ([]byte, error) {
  129. return UpdateIntakeIssue(slug, projectID, issueID, map[string]interface{}{
  130. "status": 1,
  131. })
  132. }
  133. // RejectIntakeIssue 拒绝收件箱工作项
  134. func RejectIntakeIssue(slug, projectID, issueID string) ([]byte, error) {
  135. return UpdateIntakeIssue(slug, projectID, issueID, map[string]interface{}{
  136. "status": -1,
  137. })
  138. }
  139. // SnoozeIntakeIssue 暂缓收件箱工作项
  140. func SnoozeIntakeIssue(slug, projectID, issueID, snoozedTill string) ([]byte, error) {
  141. return UpdateIntakeIssue(slug, projectID, issueID, map[string]interface{}{
  142. "status": 0,
  143. "snoozed_till": snoozedTill,
  144. })
  145. }
  146. // DuplicateIntakeIssue 标记收件箱工作项为重复
  147. func DuplicateIntakeIssue(slug, projectID, issueID, duplicateTo string) ([]byte, error) {
  148. return UpdateIntakeIssue(slug, projectID, issueID, map[string]interface{}{
  149. "status": 2,
  150. "duplicate_to": duplicateTo,
  151. })
  152. }
  153. // DeleteIntakeIssue 删除收件箱工作项
  154. func DeleteIntakeIssue(slug, projectID, issueID string) error {
  155. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/intake-issues/%s/", slug, projectID, issueID)
  156. resp, err := planeWebDelete(url)
  157. if err != nil {
  158. zap.L().Error("[PlaneWeb] DeleteIntakeIssue 请求失败", zap.Error(err))
  159. return err
  160. }
  161. if resp.StatusCode != 204 {
  162. zap.L().Error("[PlaneWeb] DeleteIntakeIssue 返回错误", zap.String("status", resp.Status))
  163. return fmt.Errorf("DeleteIntakeIssue 失败: %s", resp.Status)
  164. }
  165. return nil
  166. }
  167. // ListCurrentIntakeIssues 列出当前项目的收件箱工作项
  168. func ListCurrentIntakeIssues(status string) ([]byte, error) {
  169. return ListIntakeIssues(webWorkspaceSlug, webProjectId, status)
  170. }
  171. // CreateCurrentIntakeIssue 在当前项目创建收件箱工作项
  172. func CreateCurrentIntakeIssue(issue WebIntakeIssueRequest) (*WebIntakeIssueResponse, error) {
  173. return CreateIntakeIssue(webWorkspaceSlug, webProjectId, issue)
  174. }