service.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. package plane_v2
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "fwh-toolbox-server/config"
  6. )
  7. // PlaneService 便捷封装(绑定固定的 workspace/project,共享全局 PlaneClient)
  8. type PlaneService struct {
  9. Client *PlaneClient
  10. WorkspaceSlug string
  11. ProjectId string
  12. LabelBugId string
  13. LabelFeatureId string
  14. LabelFeedbackId string
  15. }
  16. // NewPlaneService 创建 PlaneService(须先调用 InitPlaneClient)
  17. func NewPlaneService(cfg *config.PlaneWebApiConfig) *PlaneService {
  18. if globalClient == nil {
  19. panic("[PlaneService] PlaneClient 未初始化,请先调用 InitPlaneClient")
  20. }
  21. return &PlaneService{
  22. Client: globalClient,
  23. WorkspaceSlug: cfg.WorkspaceSlug,
  24. ProjectId: cfg.ProjectId,
  25. LabelBugId: cfg.LabelBugId,
  26. LabelFeatureId: cfg.LabelFeatureId,
  27. LabelFeedbackId: cfg.LabelFeedbackId,
  28. }
  29. }
  30. // ============ Intake 便捷方法 ============
  31. func (s *PlaneService) CreateIntakeIssue(issue IntakeIssueRequest) (*IntakeIssueResponse, error) {
  32. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/intake-issues/", s.WorkspaceSlug, s.ProjectId)
  33. payload := map[string]any{"source": "IN_APP", "issue": issue}
  34. resp, err := s.Client.Post(url, payload)
  35. if err != nil {
  36. return nil, err
  37. }
  38. if !IsSuccess(resp.StatusCode) {
  39. return nil, fmt.Errorf("CreateIntakeIssue 失败: %s", resp.Status)
  40. }
  41. var result IntakeIssueResponse
  42. if err := json.Unmarshal(resp.Body, &result); err != nil {
  43. return nil, err
  44. }
  45. return &result, nil
  46. }
  47. func (s *PlaneService) ListIntakeIssues(status string) ([]byte, error) {
  48. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/intake-issues/", s.WorkspaceSlug, s.ProjectId)
  49. if status != "" {
  50. url += "?status=" + status
  51. }
  52. resp, err := s.Client.Get(url)
  53. if err != nil {
  54. return nil, err
  55. }
  56. if !IsSuccess(resp.StatusCode) {
  57. return nil, fmt.Errorf("ListIntakeIssues 失败: %s", resp.Status)
  58. }
  59. return resp.Body, nil
  60. }
  61. func (s *PlaneService) GetIntakeIssue(issueID string) ([]byte, error) {
  62. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/intake-issues/%s/?expand=issue_inbox", s.WorkspaceSlug, s.ProjectId, issueID)
  63. resp, err := s.Client.Get(url)
  64. if err != nil {
  65. return nil, err
  66. }
  67. if !IsSuccess(resp.StatusCode) {
  68. return nil, fmt.Errorf("GetIntakeIssue 失败: %s", resp.Status)
  69. }
  70. return resp.Body, nil
  71. }
  72. // ============ Asset 便捷方法 ============
  73. func (s *PlaneService) UploadProjectAsset(fileName, mimeType string, fileData []byte, entityType string) (*AssetUploadResult, error) {
  74. // Step 1: 创建资源
  75. createURL := fmt.Sprintf("/api/assets/v2/workspaces/%s/projects/%s/", s.WorkspaceSlug, s.ProjectId)
  76. createPayload := map[string]any{
  77. "name": fileName, "type": mimeType, "size": len(fileData),
  78. "entity_type": entityType, "entity_identifier": "",
  79. }
  80. resp, err := s.Client.Post(createURL, createPayload)
  81. if err != nil {
  82. return nil, err
  83. }
  84. if !IsSuccess(resp.StatusCode) {
  85. return nil, fmt.Errorf("CreateProjectAsset 失败: %s", resp.Status)
  86. }
  87. var result AssetUploadResult
  88. if err := json.Unmarshal(resp.Body, &result); err != nil {
  89. return nil, err
  90. }
  91. // Step 2: 上传到 S3
  92. if err := s.Client.PostS3(result.UploadData.URL, result.UploadData.Fields, fileData, fileName); err != nil {
  93. return nil, err
  94. }
  95. // Step 3: 确认
  96. confirmURL := fmt.Sprintf("/api/assets/v2/workspaces/%s/projects/%s/%s/", s.WorkspaceSlug, s.ProjectId, result.AssetID)
  97. s.Client.Patch(confirmURL, map[string]any{})
  98. return &result, nil
  99. }
  100. func (s *PlaneService) GetProjectAssetURL(assetID string) (string, error) {
  101. url := fmt.Sprintf("/api/assets/v2/workspaces/%s/projects/%s/%s/", s.WorkspaceSlug, s.ProjectId, assetID)
  102. return s.Client.GetRedirectLocation(url)
  103. }
  104. func (s *PlaneService) DeleteProjectAsset(assetID string) error {
  105. url := fmt.Sprintf("/api/assets/v2/workspaces/%s/projects/%s/%s/", s.WorkspaceSlug, s.ProjectId, assetID)
  106. resp, err := s.Client.Delete(url)
  107. if err != nil {
  108. return err
  109. }
  110. if resp.StatusCode != 204 {
  111. return fmt.Errorf("DeleteProjectAsset 失败: %s", resp.Status)
  112. }
  113. return nil
  114. }
  115. func (s *PlaneService) BindAssetsToEntity(entityID string, assetIDs []string) error {
  116. url := fmt.Sprintf("/api/assets/v2/workspaces/%s/projects/%s/%s/bulk/", s.WorkspaceSlug, s.ProjectId, entityID)
  117. resp, err := s.Client.Post(url, map[string]any{"asset_ids": assetIDs})
  118. if err != nil {
  119. return err
  120. }
  121. if resp.StatusCode != 204 && !IsSuccess(resp.StatusCode) {
  122. return fmt.Errorf("BindAssetsToEntity 失败: %s", resp.Status)
  123. }
  124. return nil
  125. }
  126. // ============ Page 便捷方法 ============
  127. func (s *PlaneService) ListPages() ([]byte, error) {
  128. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/", s.WorkspaceSlug, s.ProjectId)
  129. resp, err := s.Client.Get(url)
  130. if err != nil {
  131. return nil, err
  132. }
  133. if !IsSuccess(resp.StatusCode) {
  134. return nil, fmt.Errorf("ListPages 失败: %s", resp.Status)
  135. }
  136. return resp.Body, nil
  137. }
  138. func (s *PlaneService) GetPage(pageID string) ([]byte, error) {
  139. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/%s/", s.WorkspaceSlug, s.ProjectId, pageID)
  140. resp, err := s.Client.Get(url)
  141. if err != nil {
  142. return nil, err
  143. }
  144. if !IsSuccess(resp.StatusCode) {
  145. return nil, fmt.Errorf("GetPage 失败: %s", resp.Status)
  146. }
  147. return resp.Body, nil
  148. }
  149. func (s *PlaneService) GetPageDescription(pageID string) ([]byte, error) {
  150. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/%s/description/", s.WorkspaceSlug, s.ProjectId, pageID)
  151. resp, err := s.Client.Get(url)
  152. if err != nil {
  153. return nil, err
  154. }
  155. if !IsSuccess(resp.StatusCode) {
  156. return nil, fmt.Errorf("GetPageDescription 失败: %s", resp.Status)
  157. }
  158. return resp.Body, nil
  159. }