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