service.go 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288
  1. package plane_v2
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. )
  6. // PlaneService 便捷封装(绑定固定的 workspace/project,共享全局 PlaneClient)
  7. type PlaneService struct {
  8. Client *PlaneClient
  9. WorkspaceSlug string
  10. ProjectId string
  11. LabelBugId string
  12. LabelFeatureId string
  13. LabelFeedbackId string
  14. }
  15. // NewPlaneService 创建 PlaneService(须先调用 InitPlaneClient)
  16. func NewPlaneService(workspaceSlug, projectId, labelBugId, labelFeatureId, labelFeedbackId string) *PlaneService {
  17. if globalClient == nil {
  18. panic("[PlaneService] PlaneClient 未初始化,请先调用 InitPlaneClient")
  19. }
  20. return &PlaneService{
  21. Client: globalClient,
  22. WorkspaceSlug: workspaceSlug,
  23. ProjectId: projectId,
  24. LabelBugId: labelBugId,
  25. LabelFeatureId: labelFeatureId,
  26. LabelFeedbackId: labelFeedbackId,
  27. }
  28. }
  29. // ============ Intake 便捷方法 ============
  30. func (s *PlaneService) CreateIntakeIssue(issue IntakeIssueRequest) (*IntakeIssueResponse, error) {
  31. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/intake-issues/", s.WorkspaceSlug, s.ProjectId)
  32. payload := map[string]any{"source": "IN_APP", "issue": issue}
  33. resp, err := s.Client.Post(url, payload)
  34. if err != nil {
  35. return nil, err
  36. }
  37. if !IsSuccess(resp.StatusCode) {
  38. return nil, fmt.Errorf("CreateIntakeIssue 失败: %s", resp.Status)
  39. }
  40. var result IntakeIssueResponse
  41. if err := json.Unmarshal(resp.Body, &result); err != nil {
  42. return nil, err
  43. }
  44. return &result, nil
  45. }
  46. func (s *PlaneService) ListIntakeIssues(status string) ([]byte, error) {
  47. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/intake-issues/", s.WorkspaceSlug, s.ProjectId)
  48. if status != "" {
  49. url += "?status=" + status
  50. }
  51. resp, err := s.Client.Get(url)
  52. if err != nil {
  53. return nil, err
  54. }
  55. if !IsSuccess(resp.StatusCode) {
  56. return nil, fmt.Errorf("ListIntakeIssues 失败: %s", resp.Status)
  57. }
  58. return resp.Body, nil
  59. }
  60. func (s *PlaneService) GetIntakeIssue(issueID string) ([]byte, error) {
  61. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/intake-issues/%s/?expand=issue_inbox", s.WorkspaceSlug, s.ProjectId, issueID)
  62. resp, err := s.Client.Get(url)
  63. if err != nil {
  64. return nil, err
  65. }
  66. if !IsSuccess(resp.StatusCode) {
  67. return nil, fmt.Errorf("GetIntakeIssue 失败: %s", resp.Status)
  68. }
  69. return resp.Body, nil
  70. }
  71. // ============ Asset 便捷方法 ============
  72. func (s *PlaneService) UploadProjectAsset(fileName, mimeType string, fileData []byte, entityType string) (*AssetUploadResult, error) {
  73. // Step 1: 创建资源
  74. createURL := fmt.Sprintf("/api/assets/v2/workspaces/%s/projects/%s/", s.WorkspaceSlug, s.ProjectId)
  75. createPayload := map[string]any{
  76. "name": fileName, "type": mimeType, "size": len(fileData),
  77. "entity_type": entityType, "entity_identifier": "",
  78. }
  79. resp, err := s.Client.Post(createURL, createPayload)
  80. if err != nil {
  81. return nil, err
  82. }
  83. if !IsSuccess(resp.StatusCode) {
  84. return nil, fmt.Errorf("CreateProjectAsset 失败: %s", resp.Status)
  85. }
  86. var result AssetUploadResult
  87. if err := json.Unmarshal(resp.Body, &result); err != nil {
  88. return nil, err
  89. }
  90. // Step 2: 上传到 S3
  91. if err := s.Client.PostS3(result.UploadData.URL, result.UploadData.Fields, fileData, fileName); err != nil {
  92. return nil, err
  93. }
  94. // Step 3: 确认
  95. confirmURL := fmt.Sprintf("/api/assets/v2/workspaces/%s/projects/%s/%s/", s.WorkspaceSlug, s.ProjectId, result.AssetID)
  96. s.Client.Patch(confirmURL, map[string]any{})
  97. return &result, nil
  98. }
  99. func (s *PlaneService) GetProjectAssetURL(assetID string) (string, error) {
  100. url := fmt.Sprintf("/api/assets/v2/workspaces/%s/projects/%s/%s/", s.WorkspaceSlug, s.ProjectId, assetID)
  101. return s.Client.GetRedirectLocation(url)
  102. }
  103. func (s *PlaneService) DeleteProjectAsset(assetID string) error {
  104. url := fmt.Sprintf("/api/assets/v2/workspaces/%s/projects/%s/%s/", s.WorkspaceSlug, s.ProjectId, assetID)
  105. resp, err := s.Client.Delete(url)
  106. if err != nil {
  107. return err
  108. }
  109. if resp.StatusCode != 204 {
  110. return fmt.Errorf("DeleteProjectAsset 失败: %s", resp.Status)
  111. }
  112. return nil
  113. }
  114. func (s *PlaneService) BindAssetsToEntity(entityID string, assetIDs []string) error {
  115. url := fmt.Sprintf("/api/assets/v2/workspaces/%s/projects/%s/%s/bulk/", s.WorkspaceSlug, s.ProjectId, entityID)
  116. resp, err := s.Client.Post(url, map[string]any{"asset_ids": assetIDs})
  117. if err != nil {
  118. return err
  119. }
  120. if resp.StatusCode != 204 && !IsSuccess(resp.StatusCode) {
  121. return fmt.Errorf("BindAssetsToEntity 失败: %s", resp.Status)
  122. }
  123. return nil
  124. }
  125. // ============ Page 便捷方法 ============
  126. func (s *PlaneService) ListPages() ([]byte, error) {
  127. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/", s.WorkspaceSlug, s.ProjectId)
  128. resp, err := s.Client.Get(url)
  129. if err != nil {
  130. return nil, err
  131. }
  132. if !IsSuccess(resp.StatusCode) {
  133. return nil, fmt.Errorf("ListPages 失败: %s", resp.Status)
  134. }
  135. return resp.Body, nil
  136. }
  137. func (s *PlaneService) GetPage(pageID string) ([]byte, error) {
  138. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/%s/", s.WorkspaceSlug, s.ProjectId, pageID)
  139. resp, err := s.Client.Get(url)
  140. if err != nil {
  141. return nil, err
  142. }
  143. if !IsSuccess(resp.StatusCode) {
  144. return nil, fmt.Errorf("GetPage 失败: %s", resp.Status)
  145. }
  146. return resp.Body, nil
  147. }
  148. func (s *PlaneService) GetPageDescription(pageID string) ([]byte, error) {
  149. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/%s/description/", s.WorkspaceSlug, s.ProjectId, pageID)
  150. resp, err := s.Client.Get(url)
  151. if err != nil {
  152. return nil, err
  153. }
  154. if !IsSuccess(resp.StatusCode) {
  155. return nil, fmt.Errorf("GetPageDescription 失败: %s", resp.Status)
  156. }
  157. return resp.Body, nil
  158. }
  159. // ============ Workspace 便捷方法 ============
  160. func (s *PlaneService) ListWorkspaces() ([]byte, error) {
  161. resp, err := s.Client.Get("/api/workspaces/")
  162. if err != nil {
  163. return nil, err
  164. }
  165. if !IsSuccess(resp.StatusCode) {
  166. return nil, fmt.Errorf("ListWorkspaces 失败: %s", resp.Status)
  167. }
  168. return resp.Body, nil
  169. }
  170. func (s *PlaneService) GetWorkspace() ([]byte, error) {
  171. url := fmt.Sprintf("/api/workspaces/%s/", s.WorkspaceSlug)
  172. resp, err := s.Client.Get(url)
  173. if err != nil {
  174. return nil, err
  175. }
  176. if !IsSuccess(resp.StatusCode) {
  177. return nil, fmt.Errorf("GetWorkspace 失败: %s", resp.Status)
  178. }
  179. return resp.Body, nil
  180. }
  181. func (s *PlaneService) CreateWorkspace(name, slug string) ([]byte, error) {
  182. payload := map[string]interface{}{"name": name, "slug": slug}
  183. resp, err := s.Client.Post("/api/workspaces/", payload)
  184. if err != nil {
  185. return nil, err
  186. }
  187. if !IsSuccess(resp.StatusCode) {
  188. return nil, fmt.Errorf("CreateWorkspace 失败: %s, body=%s", resp.Status, string(resp.Body))
  189. }
  190. return resp.Body, nil
  191. }
  192. func (s *PlaneService) UpdateWorkspace(data map[string]interface{}) ([]byte, error) {
  193. url := fmt.Sprintf("/api/workspaces/%s/", s.WorkspaceSlug)
  194. resp, err := s.Client.Patch(url, data)
  195. if err != nil {
  196. return nil, err
  197. }
  198. if !IsSuccess(resp.StatusCode) {
  199. return nil, fmt.Errorf("UpdateWorkspace 失败: %s, body=%s", resp.Status, string(resp.Body))
  200. }
  201. return resp.Body, nil
  202. }
  203. func (s *PlaneService) DeleteWorkspace() error {
  204. url := fmt.Sprintf("/api/workspaces/%s/", s.WorkspaceSlug)
  205. resp, err := s.Client.Delete(url)
  206. if err != nil {
  207. return err
  208. }
  209. if resp.StatusCode != 204 {
  210. return fmt.Errorf("DeleteWorkspace 失败: %s", resp.Status)
  211. }
  212. return nil
  213. }
  214. // ============ Project 便捷方法 ============
  215. func (s *PlaneService) ListProjects() ([]byte, error) {
  216. url := fmt.Sprintf("/api/workspaces/%s/projects/", s.WorkspaceSlug)
  217. resp, err := s.Client.Get(url)
  218. if err != nil {
  219. return nil, err
  220. }
  221. if !IsSuccess(resp.StatusCode) {
  222. return nil, fmt.Errorf("ListProjects 失败: %s", resp.Status)
  223. }
  224. return resp.Body, nil
  225. }
  226. func (s *PlaneService) GetProject(projectID string) ([]byte, error) {
  227. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/", s.WorkspaceSlug, projectID)
  228. resp, err := s.Client.Get(url)
  229. if err != nil {
  230. return nil, err
  231. }
  232. if !IsSuccess(resp.StatusCode) {
  233. return nil, fmt.Errorf("GetProject 失败: %s", resp.Status)
  234. }
  235. return resp.Body, nil
  236. }
  237. func (s *PlaneService) CreateProject(data map[string]interface{}) ([]byte, error) {
  238. url := fmt.Sprintf("/api/workspaces/%s/projects/", s.WorkspaceSlug)
  239. resp, err := s.Client.Post(url, data)
  240. if err != nil {
  241. return nil, err
  242. }
  243. if !IsSuccess(resp.StatusCode) {
  244. return nil, fmt.Errorf("CreateProject 失败: %s, body=%s", resp.Status, string(resp.Body))
  245. }
  246. return resp.Body, nil
  247. }
  248. func (s *PlaneService) UpdateProject(projectID string, data map[string]interface{}) ([]byte, error) {
  249. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/", s.WorkspaceSlug, projectID)
  250. resp, err := s.Client.Patch(url, data)
  251. if err != nil {
  252. return nil, err
  253. }
  254. if !IsSuccess(resp.StatusCode) {
  255. return nil, fmt.Errorf("UpdateProject 失败: %s, body=%s", resp.Status, string(resp.Body))
  256. }
  257. return resp.Body, nil
  258. }
  259. func (s *PlaneService) DeleteProject(projectID string) error {
  260. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/", s.WorkspaceSlug, projectID)
  261. resp, err := s.Client.Delete(url)
  262. if err != nil {
  263. return err
  264. }
  265. if resp.StatusCode != 204 {
  266. return fmt.Errorf("DeleteProject 失败: %s", resp.Status)
  267. }
  268. return nil
  269. }
  270. func (s *PlaneService) ArchiveProject(projectID string) error {
  271. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/archive/", s.WorkspaceSlug, projectID)
  272. resp, err := s.Client.Post(url, nil)
  273. if err != nil {
  274. return err
  275. }
  276. if !IsSuccess(resp.StatusCode) {
  277. return fmt.Errorf("ArchiveProject 失败: %s", resp.Status)
  278. }
  279. return nil
  280. }
  281. func (s *PlaneService) UnarchiveProject(projectID string) error {
  282. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/archive/", s.WorkspaceSlug, projectID)
  283. resp, err := s.Client.Delete(url)
  284. if err != nil {
  285. return err
  286. }
  287. if !IsSuccess(resp.StatusCode) {
  288. return fmt.Errorf("UnarchiveProject 失败: %s", resp.Status)
  289. }
  290. return nil
  291. }
  292. // ============ Issue 便捷方法 ============
  293. func (s *PlaneService) ListIssues() ([]byte, error) {
  294. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/", s.WorkspaceSlug, s.ProjectId)
  295. resp, err := s.Client.Get(url)
  296. if err != nil {
  297. return nil, err
  298. }
  299. if !IsSuccess(resp.StatusCode) {
  300. return nil, fmt.Errorf("ListIssues 失败: %s", resp.Status)
  301. }
  302. return resp.Body, nil
  303. }
  304. func (s *PlaneService) GetIssue(issueID string) ([]byte, error) {
  305. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/", s.WorkspaceSlug, s.ProjectId, issueID)
  306. resp, err := s.Client.Get(url)
  307. if err != nil {
  308. return nil, err
  309. }
  310. if !IsSuccess(resp.StatusCode) {
  311. return nil, fmt.Errorf("GetIssue 失败: %s", resp.Status)
  312. }
  313. return resp.Body, nil
  314. }
  315. func (s *PlaneService) CreateIssue(data map[string]interface{}) ([]byte, error) {
  316. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/", s.WorkspaceSlug, s.ProjectId)
  317. resp, err := s.Client.Post(url, data)
  318. if err != nil {
  319. return nil, err
  320. }
  321. if !IsSuccess(resp.StatusCode) {
  322. return nil, fmt.Errorf("CreateIssue 失败: %s, body=%s", resp.Status, string(resp.Body))
  323. }
  324. return resp.Body, nil
  325. }
  326. func (s *PlaneService) UpdateIssue(issueID string, data map[string]interface{}) ([]byte, error) {
  327. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/", s.WorkspaceSlug, s.ProjectId, issueID)
  328. resp, err := s.Client.Patch(url, data)
  329. if err != nil {
  330. return nil, err
  331. }
  332. if !IsSuccess(resp.StatusCode) {
  333. return nil, fmt.Errorf("UpdateIssue 失败: %s, body=%s", resp.Status, string(resp.Body))
  334. }
  335. return resp.Body, nil
  336. }
  337. func (s *PlaneService) DeleteIssue(issueID string) error {
  338. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/", s.WorkspaceSlug, s.ProjectId, issueID)
  339. resp, err := s.Client.Delete(url)
  340. if err != nil {
  341. return err
  342. }
  343. if resp.StatusCode != 204 {
  344. return fmt.Errorf("DeleteIssue 失败: %s", resp.Status)
  345. }
  346. return nil
  347. }
  348. func (s *PlaneService) GetSubIssues(issueID string) ([]byte, error) {
  349. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/sub-issues/", s.WorkspaceSlug, s.ProjectId, issueID)
  350. resp, err := s.Client.Get(url)
  351. if err != nil {
  352. return nil, err
  353. }
  354. if !IsSuccess(resp.StatusCode) {
  355. return nil, fmt.Errorf("GetSubIssues 失败: %s", resp.Status)
  356. }
  357. return resp.Body, nil
  358. }
  359. func (s *PlaneService) ArchiveIssue(issueID string) error {
  360. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/archive/", s.WorkspaceSlug, s.ProjectId, issueID)
  361. resp, err := s.Client.Post(url, nil)
  362. if err != nil {
  363. return err
  364. }
  365. if !IsSuccess(resp.StatusCode) {
  366. return fmt.Errorf("ArchiveIssue 失败: %s", resp.Status)
  367. }
  368. return nil
  369. }
  370. func (s *PlaneService) UnarchiveIssue(issueID string) error {
  371. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/archive/", s.WorkspaceSlug, s.ProjectId, issueID)
  372. resp, err := s.Client.Delete(url)
  373. if err != nil {
  374. return err
  375. }
  376. if !IsSuccess(resp.StatusCode) {
  377. return fmt.Errorf("UnarchiveIssue 失败: %s", resp.Status)
  378. }
  379. return nil
  380. }
  381. func (s *PlaneService) GetIssueActivity(issueID string) ([]byte, error) {
  382. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/history/?activity_type=issue-property", s.WorkspaceSlug, s.ProjectId, issueID)
  383. resp, err := s.Client.Get(url)
  384. if err != nil {
  385. return nil, err
  386. }
  387. if !IsSuccess(resp.StatusCode) {
  388. return nil, fmt.Errorf("GetIssueActivity 失败: %s", resp.Status)
  389. }
  390. return resp.Body, nil
  391. }
  392. // ============ Cycle 便捷方法 ============
  393. func (s *PlaneService) ListCycles() ([]byte, error) {
  394. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/", s.WorkspaceSlug, s.ProjectId)
  395. resp, err := s.Client.Get(url)
  396. if err != nil {
  397. return nil, err
  398. }
  399. if !IsSuccess(resp.StatusCode) {
  400. return nil, fmt.Errorf("ListCycles 失败: %s", resp.Status)
  401. }
  402. return resp.Body, nil
  403. }
  404. func (s *PlaneService) GetCycle(cycleID string) ([]byte, error) {
  405. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/", s.WorkspaceSlug, s.ProjectId, cycleID)
  406. resp, err := s.Client.Get(url)
  407. if err != nil {
  408. return nil, err
  409. }
  410. if !IsSuccess(resp.StatusCode) {
  411. return nil, fmt.Errorf("GetCycle 失败: %s", resp.Status)
  412. }
  413. return resp.Body, nil
  414. }
  415. func (s *PlaneService) CreateCycle(data map[string]interface{}) ([]byte, error) {
  416. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/", s.WorkspaceSlug, s.ProjectId)
  417. resp, err := s.Client.Post(url, data)
  418. if err != nil {
  419. return nil, err
  420. }
  421. if !IsSuccess(resp.StatusCode) {
  422. return nil, fmt.Errorf("CreateCycle 失败: %s, body=%s", resp.Status, string(resp.Body))
  423. }
  424. return resp.Body, nil
  425. }
  426. func (s *PlaneService) UpdateCycle(cycleID string, data map[string]interface{}) ([]byte, error) {
  427. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/", s.WorkspaceSlug, s.ProjectId, cycleID)
  428. resp, err := s.Client.Patch(url, data)
  429. if err != nil {
  430. return nil, err
  431. }
  432. if !IsSuccess(resp.StatusCode) {
  433. return nil, fmt.Errorf("UpdateCycle 失败: %s, body=%s", resp.Status, string(resp.Body))
  434. }
  435. return resp.Body, nil
  436. }
  437. func (s *PlaneService) DeleteCycle(cycleID string) error {
  438. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/", s.WorkspaceSlug, s.ProjectId, cycleID)
  439. resp, err := s.Client.Delete(url)
  440. if err != nil {
  441. return err
  442. }
  443. if resp.StatusCode != 204 {
  444. return fmt.Errorf("DeleteCycle 失败: %s", resp.Status)
  445. }
  446. return nil
  447. }
  448. func (s *PlaneService) ListCycleIssues(cycleID string) ([]byte, error) {
  449. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/cycle-issues/", s.WorkspaceSlug, s.ProjectId, cycleID)
  450. resp, err := s.Client.Get(url)
  451. if err != nil {
  452. return nil, err
  453. }
  454. if !IsSuccess(resp.StatusCode) {
  455. return nil, fmt.Errorf("ListCycleIssues 失败: %s", resp.Status)
  456. }
  457. return resp.Body, nil
  458. }
  459. func (s *PlaneService) AddIssuesToCycle(cycleID string, issueIDs []string) ([]byte, error) {
  460. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/cycle-issues/", s.WorkspaceSlug, s.ProjectId, cycleID)
  461. payload := map[string]interface{}{"issues": issueIDs}
  462. resp, err := s.Client.Post(url, payload)
  463. if err != nil {
  464. return nil, err
  465. }
  466. if !IsSuccess(resp.StatusCode) {
  467. return nil, fmt.Errorf("AddIssuesToCycle 失败: %s, body=%s", resp.Status, string(resp.Body))
  468. }
  469. return resp.Body, nil
  470. }
  471. func (s *PlaneService) RemoveIssueFromCycle(cycleID, issueID string) error {
  472. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/cycle-issues/%s/", s.WorkspaceSlug, s.ProjectId, cycleID, issueID)
  473. resp, err := s.Client.Delete(url)
  474. if err != nil {
  475. return err
  476. }
  477. if resp.StatusCode != 204 {
  478. return fmt.Errorf("RemoveIssueFromCycle 失败: %s", resp.Status)
  479. }
  480. return nil
  481. }
  482. func (s *PlaneService) ArchiveCycle(cycleID string) error {
  483. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/archive/", s.WorkspaceSlug, s.ProjectId, cycleID)
  484. resp, err := s.Client.Post(url, nil)
  485. if err != nil {
  486. return err
  487. }
  488. if !IsSuccess(resp.StatusCode) {
  489. return fmt.Errorf("ArchiveCycle 失败: %s", resp.Status)
  490. }
  491. return nil
  492. }
  493. func (s *PlaneService) UnarchiveCycle(cycleID string) error {
  494. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/cycles/%s/archive/", s.WorkspaceSlug, s.ProjectId, cycleID)
  495. resp, err := s.Client.Delete(url)
  496. if err != nil {
  497. return err
  498. }
  499. if !IsSuccess(resp.StatusCode) {
  500. return fmt.Errorf("UnarchiveCycle 失败: %s", resp.Status)
  501. }
  502. return nil
  503. }
  504. // ============ Module 便捷方法 ============
  505. func (s *PlaneService) ListModules() ([]byte, error) {
  506. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/", s.WorkspaceSlug, s.ProjectId)
  507. resp, err := s.Client.Get(url)
  508. if err != nil {
  509. return nil, err
  510. }
  511. if !IsSuccess(resp.StatusCode) {
  512. return nil, fmt.Errorf("ListModules 失败: %s", resp.Status)
  513. }
  514. return resp.Body, nil
  515. }
  516. func (s *PlaneService) GetModule(moduleID string) ([]byte, error) {
  517. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/%s/", s.WorkspaceSlug, s.ProjectId, moduleID)
  518. resp, err := s.Client.Get(url)
  519. if err != nil {
  520. return nil, err
  521. }
  522. if !IsSuccess(resp.StatusCode) {
  523. return nil, fmt.Errorf("GetModule 失败: %s", resp.Status)
  524. }
  525. return resp.Body, nil
  526. }
  527. func (s *PlaneService) CreateModule(data map[string]interface{}) ([]byte, error) {
  528. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/", s.WorkspaceSlug, s.ProjectId)
  529. resp, err := s.Client.Post(url, data)
  530. if err != nil {
  531. return nil, err
  532. }
  533. if !IsSuccess(resp.StatusCode) {
  534. return nil, fmt.Errorf("CreateModule 失败: %s, body=%s", resp.Status, string(resp.Body))
  535. }
  536. return resp.Body, nil
  537. }
  538. func (s *PlaneService) UpdateModule(moduleID string, data map[string]interface{}) ([]byte, error) {
  539. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/%s/", s.WorkspaceSlug, s.ProjectId, moduleID)
  540. resp, err := s.Client.Patch(url, data)
  541. if err != nil {
  542. return nil, err
  543. }
  544. if !IsSuccess(resp.StatusCode) {
  545. return nil, fmt.Errorf("UpdateModule 失败: %s, body=%s", resp.Status, string(resp.Body))
  546. }
  547. return resp.Body, nil
  548. }
  549. func (s *PlaneService) DeleteModule(moduleID string) error {
  550. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/%s/", s.WorkspaceSlug, s.ProjectId, moduleID)
  551. resp, err := s.Client.Delete(url)
  552. if err != nil {
  553. return err
  554. }
  555. if resp.StatusCode != 204 {
  556. return fmt.Errorf("DeleteModule 失败: %s", resp.Status)
  557. }
  558. return nil
  559. }
  560. func (s *PlaneService) ListModuleIssues(moduleID string) ([]byte, error) {
  561. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/%s/module-issues/", s.WorkspaceSlug, s.ProjectId, moduleID)
  562. resp, err := s.Client.Get(url)
  563. if err != nil {
  564. return nil, err
  565. }
  566. if !IsSuccess(resp.StatusCode) {
  567. return nil, fmt.Errorf("ListModuleIssues 失败: %s", resp.Status)
  568. }
  569. return resp.Body, nil
  570. }
  571. func (s *PlaneService) AddIssuesToModule(moduleID string, issueIDs []string) ([]byte, error) {
  572. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/%s/module-issues/", s.WorkspaceSlug, s.ProjectId, moduleID)
  573. payload := map[string]interface{}{"issues": issueIDs}
  574. resp, err := s.Client.Post(url, payload)
  575. if err != nil {
  576. return nil, err
  577. }
  578. if !IsSuccess(resp.StatusCode) {
  579. return nil, fmt.Errorf("AddIssuesToModule 失败: %s, body=%s", resp.Status, string(resp.Body))
  580. }
  581. return resp.Body, nil
  582. }
  583. func (s *PlaneService) RemoveIssueFromModule(moduleID, issueID string) error {
  584. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/%s/module-issues/%s/", s.WorkspaceSlug, s.ProjectId, moduleID, issueID)
  585. resp, err := s.Client.Delete(url)
  586. if err != nil {
  587. return err
  588. }
  589. if resp.StatusCode != 204 {
  590. return fmt.Errorf("RemoveIssueFromModule 失败: %s", resp.Status)
  591. }
  592. return nil
  593. }
  594. func (s *PlaneService) ArchiveModule(moduleID string) error {
  595. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/%s/archive/", s.WorkspaceSlug, s.ProjectId, moduleID)
  596. resp, err := s.Client.Post(url, nil)
  597. if err != nil {
  598. return err
  599. }
  600. if !IsSuccess(resp.StatusCode) {
  601. return fmt.Errorf("ArchiveModule 失败: %s", resp.Status)
  602. }
  603. return nil
  604. }
  605. func (s *PlaneService) UnarchiveModule(moduleID string) error {
  606. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/modules/%s/archive/", s.WorkspaceSlug, s.ProjectId, moduleID)
  607. resp, err := s.Client.Delete(url)
  608. if err != nil {
  609. return err
  610. }
  611. if !IsSuccess(resp.StatusCode) {
  612. return fmt.Errorf("UnarchiveModule 失败: %s", resp.Status)
  613. }
  614. return nil
  615. }
  616. // ============ Label 便捷方法 ============
  617. func (s *PlaneService) ListLabels() ([]byte, error) {
  618. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/labels/", s.WorkspaceSlug, s.ProjectId)
  619. resp, err := s.Client.Get(url)
  620. if err != nil {
  621. return nil, err
  622. }
  623. if !IsSuccess(resp.StatusCode) {
  624. return nil, fmt.Errorf("ListLabels 失败: %s", resp.Status)
  625. }
  626. return resp.Body, nil
  627. }
  628. func (s *PlaneService) GetLabel(labelID string) ([]byte, error) {
  629. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/labels/%s/", s.WorkspaceSlug, s.ProjectId, labelID)
  630. resp, err := s.Client.Get(url)
  631. if err != nil {
  632. return nil, err
  633. }
  634. if !IsSuccess(resp.StatusCode) {
  635. return nil, fmt.Errorf("GetLabel 失败: %s", resp.Status)
  636. }
  637. return resp.Body, nil
  638. }
  639. func (s *PlaneService) CreateLabel(name, color string) ([]byte, error) {
  640. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/labels/", s.WorkspaceSlug, s.ProjectId)
  641. payload := map[string]interface{}{"name": name, "color": color}
  642. resp, err := s.Client.Post(url, payload)
  643. if err != nil {
  644. return nil, err
  645. }
  646. if !IsSuccess(resp.StatusCode) {
  647. return nil, fmt.Errorf("CreateLabel 失败: %s, body=%s", resp.Status, string(resp.Body))
  648. }
  649. return resp.Body, nil
  650. }
  651. func (s *PlaneService) UpdateLabel(labelID string, data map[string]interface{}) ([]byte, error) {
  652. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/labels/%s/", s.WorkspaceSlug, s.ProjectId, labelID)
  653. resp, err := s.Client.Patch(url, data)
  654. if err != nil {
  655. return nil, err
  656. }
  657. if !IsSuccess(resp.StatusCode) {
  658. return nil, fmt.Errorf("UpdateLabel 失败: %s, body=%s", resp.Status, string(resp.Body))
  659. }
  660. return resp.Body, nil
  661. }
  662. func (s *PlaneService) DeleteLabel(labelID string) error {
  663. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/labels/%s/", s.WorkspaceSlug, s.ProjectId, labelID)
  664. resp, err := s.Client.Delete(url)
  665. if err != nil {
  666. return err
  667. }
  668. if resp.StatusCode != 204 {
  669. return fmt.Errorf("DeleteLabel 失败: %s", resp.Status)
  670. }
  671. return nil
  672. }
  673. // ============ State 便捷方法 ============
  674. func (s *PlaneService) ListStates() ([]byte, error) {
  675. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/states/", s.WorkspaceSlug, s.ProjectId)
  676. resp, err := s.Client.Get(url)
  677. if err != nil {
  678. return nil, err
  679. }
  680. if !IsSuccess(resp.StatusCode) {
  681. return nil, fmt.Errorf("ListStates 失败: %s", resp.Status)
  682. }
  683. return resp.Body, nil
  684. }
  685. func (s *PlaneService) GetState(stateID string) ([]byte, error) {
  686. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/states/%s/", s.WorkspaceSlug, s.ProjectId, stateID)
  687. resp, err := s.Client.Get(url)
  688. if err != nil {
  689. return nil, err
  690. }
  691. if !IsSuccess(resp.StatusCode) {
  692. return nil, fmt.Errorf("GetState 失败: %s", resp.Status)
  693. }
  694. return resp.Body, nil
  695. }
  696. func (s *PlaneService) CreateState(name, color, group string) ([]byte, error) {
  697. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/states/", s.WorkspaceSlug, s.ProjectId)
  698. payload := map[string]interface{}{"name": name, "color": color, "group": group}
  699. resp, err := s.Client.Post(url, payload)
  700. if err != nil {
  701. return nil, err
  702. }
  703. if !IsSuccess(resp.StatusCode) {
  704. return nil, fmt.Errorf("CreateState 失败: %s, body=%s", resp.Status, string(resp.Body))
  705. }
  706. return resp.Body, nil
  707. }
  708. func (s *PlaneService) UpdateState(stateID string, data map[string]interface{}) ([]byte, error) {
  709. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/states/%s/", s.WorkspaceSlug, s.ProjectId, stateID)
  710. resp, err := s.Client.Patch(url, data)
  711. if err != nil {
  712. return nil, err
  713. }
  714. if !IsSuccess(resp.StatusCode) {
  715. return nil, fmt.Errorf("UpdateState 失败: %s, body=%s", resp.Status, string(resp.Body))
  716. }
  717. return resp.Body, nil
  718. }
  719. func (s *PlaneService) DeleteState(stateID string) error {
  720. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/states/%s/", s.WorkspaceSlug, s.ProjectId, stateID)
  721. resp, err := s.Client.Delete(url)
  722. if err != nil {
  723. return err
  724. }
  725. if resp.StatusCode != 204 {
  726. return fmt.Errorf("DeleteState 失败: %s", resp.Status)
  727. }
  728. return nil
  729. }
  730. func (s *PlaneService) MarkStateDefault(stateID string) error {
  731. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/states/%s/default/", s.WorkspaceSlug, s.ProjectId, stateID)
  732. resp, err := s.Client.Post(url, nil)
  733. if err != nil {
  734. return err
  735. }
  736. if !IsSuccess(resp.StatusCode) {
  737. return fmt.Errorf("MarkStateDefault 失败: %s", resp.Status)
  738. }
  739. return nil
  740. }
  741. // ============ Comment 便捷方法 ============
  742. func (s *PlaneService) ListComments(issueID string) ([]byte, error) {
  743. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/comments/", s.WorkspaceSlug, s.ProjectId, issueID)
  744. resp, err := s.Client.Get(url)
  745. if err != nil {
  746. return nil, err
  747. }
  748. if !IsSuccess(resp.StatusCode) {
  749. return nil, fmt.Errorf("ListComments 失败: %s", resp.Status)
  750. }
  751. return resp.Body, nil
  752. }
  753. func (s *PlaneService) GetComment(issueID, commentID string) ([]byte, error) {
  754. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/comments/%s/", s.WorkspaceSlug, s.ProjectId, issueID, commentID)
  755. resp, err := s.Client.Get(url)
  756. if err != nil {
  757. return nil, err
  758. }
  759. if !IsSuccess(resp.StatusCode) {
  760. return nil, fmt.Errorf("GetComment 失败: %s", resp.Status)
  761. }
  762. return resp.Body, nil
  763. }
  764. func (s *PlaneService) CreateComment(issueID, commentHTML string) ([]byte, error) {
  765. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/comments/", s.WorkspaceSlug, s.ProjectId, issueID)
  766. payload := map[string]interface{}{"comment_html": commentHTML}
  767. resp, err := s.Client.Post(url, payload)
  768. if err != nil {
  769. return nil, err
  770. }
  771. if !IsSuccess(resp.StatusCode) {
  772. return nil, fmt.Errorf("CreateComment 失败: %s, body=%s", resp.Status, string(resp.Body))
  773. }
  774. return resp.Body, nil
  775. }
  776. func (s *PlaneService) UpdateComment(issueID, commentID, commentHTML string) ([]byte, error) {
  777. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/comments/%s/", s.WorkspaceSlug, s.ProjectId, issueID, commentID)
  778. payload := map[string]interface{}{"comment_html": commentHTML}
  779. resp, err := s.Client.Patch(url, payload)
  780. if err != nil {
  781. return nil, err
  782. }
  783. if !IsSuccess(resp.StatusCode) {
  784. return nil, fmt.Errorf("UpdateComment 失败: %s, body=%s", resp.Status, string(resp.Body))
  785. }
  786. return resp.Body, nil
  787. }
  788. func (s *PlaneService) DeleteComment(issueID, commentID string) error {
  789. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/comments/%s/", s.WorkspaceSlug, s.ProjectId, issueID, commentID)
  790. resp, err := s.Client.Delete(url)
  791. if err != nil {
  792. return err
  793. }
  794. if resp.StatusCode != 204 {
  795. return fmt.Errorf("DeleteComment 失败: %s", resp.Status)
  796. }
  797. return nil
  798. }
  799. // ============ Link 便捷方法 ============
  800. func (s *PlaneService) ListIssueLinks(issueID string) ([]byte, error) {
  801. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/links/", s.WorkspaceSlug, s.ProjectId, issueID)
  802. resp, err := s.Client.Get(url)
  803. if err != nil {
  804. return nil, err
  805. }
  806. if !IsSuccess(resp.StatusCode) {
  807. return nil, fmt.Errorf("ListIssueLinks 失败: %s", resp.Status)
  808. }
  809. return resp.Body, nil
  810. }
  811. func (s *PlaneService) CreateIssueLink(issueID, linkURL, title string) ([]byte, error) {
  812. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/links/", s.WorkspaceSlug, s.ProjectId, issueID)
  813. payload := map[string]interface{}{"url": linkURL, "title": title}
  814. resp, err := s.Client.Post(url, payload)
  815. if err != nil {
  816. return nil, err
  817. }
  818. if !IsSuccess(resp.StatusCode) {
  819. return nil, fmt.Errorf("CreateIssueLink 失败: %s, body=%s", resp.Status, string(resp.Body))
  820. }
  821. return resp.Body, nil
  822. }
  823. func (s *PlaneService) UpdateIssueLink(issueID, linkID string, data map[string]interface{}) ([]byte, error) {
  824. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/links/%s/", s.WorkspaceSlug, s.ProjectId, issueID, linkID)
  825. resp, err := s.Client.Patch(url, data)
  826. if err != nil {
  827. return nil, err
  828. }
  829. if !IsSuccess(resp.StatusCode) {
  830. return nil, fmt.Errorf("UpdateIssueLink 失败: %s, body=%s", resp.Status, string(resp.Body))
  831. }
  832. return resp.Body, nil
  833. }
  834. func (s *PlaneService) DeleteIssueLink(issueID, linkID string) error {
  835. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/links/%s/", s.WorkspaceSlug, s.ProjectId, issueID, linkID)
  836. resp, err := s.Client.Delete(url)
  837. if err != nil {
  838. return err
  839. }
  840. if resp.StatusCode != 204 {
  841. return fmt.Errorf("DeleteIssueLink 失败: %s", resp.Status)
  842. }
  843. return nil
  844. }
  845. // ============ Relation 便捷方法 ============
  846. func (s *PlaneService) ListIssueRelations(issueID string) ([]byte, error) {
  847. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/relations/", s.WorkspaceSlug, s.ProjectId, issueID)
  848. resp, err := s.Client.Get(url)
  849. if err != nil {
  850. return nil, err
  851. }
  852. if !IsSuccess(resp.StatusCode) {
  853. return nil, fmt.Errorf("ListIssueRelations 失败: %s", resp.Status)
  854. }
  855. return resp.Body, nil
  856. }
  857. func (s *PlaneService) CreateIssueRelation(issueID, relationType string, relatedIDs []string) ([]byte, error) {
  858. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/relations/", s.WorkspaceSlug, s.ProjectId, issueID)
  859. payload := map[string]interface{}{"relation_type": relationType, "related_issues": relatedIDs}
  860. resp, err := s.Client.Post(url, payload)
  861. if err != nil {
  862. return nil, err
  863. }
  864. if !IsSuccess(resp.StatusCode) {
  865. return nil, fmt.Errorf("CreateIssueRelation 失败: %s, body=%s", resp.Status, string(resp.Body))
  866. }
  867. return resp.Body, nil
  868. }
  869. func (s *PlaneService) RemoveIssueRelation(issueID, relationType, relatedID string) error {
  870. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/issues/%s/relations/%s/%s/", s.WorkspaceSlug, s.ProjectId, issueID, relationType, relatedID)
  871. resp, err := s.Client.Delete(url)
  872. if err != nil {
  873. return err
  874. }
  875. if resp.StatusCode != 204 {
  876. return fmt.Errorf("RemoveIssueRelation 失败: %s", resp.Status)
  877. }
  878. return nil
  879. }
  880. // ============ Sticky 便捷方法 ============
  881. func (s *PlaneService) ListStickies() ([]byte, error) {
  882. url := fmt.Sprintf("/api/workspaces/%s/stickies/", s.WorkspaceSlug)
  883. resp, err := s.Client.Get(url)
  884. if err != nil {
  885. return nil, err
  886. }
  887. if !IsSuccess(resp.StatusCode) {
  888. return nil, fmt.Errorf("ListStickies 失败: %s", resp.Status)
  889. }
  890. return resp.Body, nil
  891. }
  892. func (s *PlaneService) GetSticky(stickyID string) ([]byte, error) {
  893. url := fmt.Sprintf("/api/workspaces/%s/stickies/%s/", s.WorkspaceSlug, stickyID)
  894. resp, err := s.Client.Get(url)
  895. if err != nil {
  896. return nil, err
  897. }
  898. if !IsSuccess(resp.StatusCode) {
  899. return nil, fmt.Errorf("GetSticky 失败: %s", resp.Status)
  900. }
  901. return resp.Body, nil
  902. }
  903. func (s *PlaneService) CreateSticky(data map[string]interface{}) ([]byte, error) {
  904. url := fmt.Sprintf("/api/workspaces/%s/stickies/", s.WorkspaceSlug)
  905. resp, err := s.Client.Post(url, data)
  906. if err != nil {
  907. return nil, err
  908. }
  909. if !IsSuccess(resp.StatusCode) {
  910. return nil, fmt.Errorf("CreateSticky 失败: %s, body=%s", resp.Status, string(resp.Body))
  911. }
  912. return resp.Body, nil
  913. }
  914. func (s *PlaneService) UpdateSticky(stickyID string, data map[string]interface{}) ([]byte, error) {
  915. url := fmt.Sprintf("/api/workspaces/%s/stickies/%s/", s.WorkspaceSlug, stickyID)
  916. resp, err := s.Client.Patch(url, data)
  917. if err != nil {
  918. return nil, err
  919. }
  920. if !IsSuccess(resp.StatusCode) {
  921. return nil, fmt.Errorf("UpdateSticky 失败: %s, body=%s", resp.Status, string(resp.Body))
  922. }
  923. return resp.Body, nil
  924. }
  925. func (s *PlaneService) DeleteSticky(stickyID string) error {
  926. url := fmt.Sprintf("/api/workspaces/%s/stickies/%s/", s.WorkspaceSlug, stickyID)
  927. resp, err := s.Client.Delete(url)
  928. if err != nil {
  929. return err
  930. }
  931. if resp.StatusCode != 204 {
  932. return fmt.Errorf("DeleteSticky 失败: %s", resp.Status)
  933. }
  934. return nil
  935. }
  936. // ============ Intake 补充方法 ============
  937. func (s *PlaneService) UpdateIntakeIssue(issueID string, data map[string]any) ([]byte, error) {
  938. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/intake-issues/%s/", s.WorkspaceSlug, s.ProjectId, issueID)
  939. resp, err := s.Client.Patch(url, data)
  940. if err != nil {
  941. return nil, err
  942. }
  943. if !IsSuccess(resp.StatusCode) {
  944. return nil, fmt.Errorf("UpdateIntakeIssue 失败: %s", resp.Status)
  945. }
  946. return resp.Body, nil
  947. }
  948. func (s *PlaneService) AcceptIntakeIssue(issueID string) ([]byte, error) {
  949. return s.UpdateIntakeIssue(issueID, map[string]any{"status": 1})
  950. }
  951. func (s *PlaneService) RejectIntakeIssue(issueID string) ([]byte, error) {
  952. return s.UpdateIntakeIssue(issueID, map[string]any{"status": -1})
  953. }
  954. func (s *PlaneService) DeleteIntakeIssue(issueID string) error {
  955. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/intake-issues/%s/", s.WorkspaceSlug, s.ProjectId, issueID)
  956. resp, err := s.Client.Delete(url)
  957. if err != nil {
  958. return err
  959. }
  960. if resp.StatusCode != 204 {
  961. return fmt.Errorf("DeleteIntakeIssue 失败: %s", resp.Status)
  962. }
  963. return nil
  964. }
  965. // ============ Asset 补充方法 ============
  966. func (s *PlaneService) CreateProjectAsset(fileName, mimeType string, size int64, entityType string) (*AssetUploadResult, error) {
  967. url := fmt.Sprintf("/api/assets/v2/workspaces/%s/projects/%s/", s.WorkspaceSlug, s.ProjectId)
  968. payload := map[string]interface{}{
  969. "name": fileName, "type": mimeType, "size": size,
  970. "entity_type": entityType, "entity_identifier": "",
  971. }
  972. resp, err := s.Client.Post(url, payload)
  973. if err != nil {
  974. return nil, err
  975. }
  976. if !IsSuccess(resp.StatusCode) {
  977. return nil, fmt.Errorf("CreateProjectAsset 失败: %s", resp.Status)
  978. }
  979. var result AssetUploadResult
  980. if err := json.Unmarshal(resp.Body, &result); err != nil {
  981. return nil, fmt.Errorf("解析响应失败: %w", err)
  982. }
  983. return &result, nil
  984. }
  985. func (s *PlaneService) ConfirmProjectAssetUpload(assetID string) error {
  986. url := fmt.Sprintf("/api/assets/v2/workspaces/%s/projects/%s/%s/", s.WorkspaceSlug, s.ProjectId, assetID)
  987. resp, err := s.Client.Patch(url, map[string]interface{}{})
  988. if err != nil {
  989. return err
  990. }
  991. if resp.StatusCode != 204 && !IsSuccess(resp.StatusCode) {
  992. return fmt.Errorf("ConfirmProjectAssetUpload 失败: %s", resp.Status)
  993. }
  994. return nil
  995. }
  996. func (s *PlaneService) GetProjectAsset(assetID string) ([]byte, error) {
  997. url := fmt.Sprintf("/api/assets/v2/workspaces/%s/projects/%s/%s/", s.WorkspaceSlug, s.ProjectId, assetID)
  998. resp, err := s.Client.Get(url)
  999. if err != nil {
  1000. return nil, err
  1001. }
  1002. return resp.Body, nil
  1003. }
  1004. // ============ Page 补充方法 ============
  1005. func (s *PlaneService) CreatePage(data map[string]interface{}) ([]byte, error) {
  1006. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/", s.WorkspaceSlug, s.ProjectId)
  1007. resp, err := s.Client.Post(url, data)
  1008. if err != nil {
  1009. return nil, err
  1010. }
  1011. if !IsSuccess(resp.StatusCode) {
  1012. return nil, fmt.Errorf("CreatePage 失败: %s, body=%s", resp.Status, string(resp.Body))
  1013. }
  1014. return resp.Body, nil
  1015. }
  1016. func (s *PlaneService) UpdatePage(pageID string, data map[string]interface{}) ([]byte, error) {
  1017. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/%s/", s.WorkspaceSlug, s.ProjectId, pageID)
  1018. resp, err := s.Client.Patch(url, data)
  1019. if err != nil {
  1020. return nil, err
  1021. }
  1022. if !IsSuccess(resp.StatusCode) {
  1023. return nil, fmt.Errorf("UpdatePage 失败: %s, body=%s", resp.Status, string(resp.Body))
  1024. }
  1025. return resp.Body, nil
  1026. }
  1027. func (s *PlaneService) DeletePage(pageID string) error {
  1028. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/%s/", s.WorkspaceSlug, s.ProjectId, pageID)
  1029. resp, err := s.Client.Delete(url)
  1030. if err != nil {
  1031. return err
  1032. }
  1033. if resp.StatusCode != 204 {
  1034. return fmt.Errorf("DeletePage 失败: %s", resp.Status)
  1035. }
  1036. return nil
  1037. }
  1038. func (s *PlaneService) ArchivePage(pageID string) error {
  1039. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/%s/archive/", s.WorkspaceSlug, s.ProjectId, pageID)
  1040. resp, err := s.Client.Post(url, nil)
  1041. if err != nil {
  1042. return err
  1043. }
  1044. if !IsSuccess(resp.StatusCode) {
  1045. return fmt.Errorf("ArchivePage 失败: %s", resp.Status)
  1046. }
  1047. return nil
  1048. }
  1049. func (s *PlaneService) UnarchivePage(pageID string) error {
  1050. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/pages/%s/archive/", s.WorkspaceSlug, s.ProjectId, pageID)
  1051. resp, err := s.Client.Delete(url)
  1052. if err != nil {
  1053. return err
  1054. }
  1055. if !IsSuccess(resp.StatusCode) {
  1056. return fmt.Errorf("UnarchivePage 失败: %s", resp.Status)
  1057. }
  1058. return nil
  1059. }
  1060. // ============ Member 便捷方法 ============
  1061. func (s *PlaneService) ListWorkspaceMembers() ([]byte, error) {
  1062. url := fmt.Sprintf("/api/workspaces/%s/members/", s.WorkspaceSlug)
  1063. resp, err := s.Client.Get(url)
  1064. if err != nil {
  1065. return nil, err
  1066. }
  1067. if !IsSuccess(resp.StatusCode) {
  1068. return nil, fmt.Errorf("ListWorkspaceMembers 失败: %s", resp.Status)
  1069. }
  1070. return resp.Body, nil
  1071. }
  1072. func (s *PlaneService) GetWorkspaceMember(memberID string) ([]byte, error) {
  1073. url := fmt.Sprintf("/api/workspaces/%s/members/%s/", s.WorkspaceSlug, memberID)
  1074. resp, err := s.Client.Get(url)
  1075. if err != nil {
  1076. return nil, err
  1077. }
  1078. if !IsSuccess(resp.StatusCode) {
  1079. return nil, fmt.Errorf("GetWorkspaceMember 失败: %s", resp.Status)
  1080. }
  1081. return resp.Body, nil
  1082. }
  1083. func (s *PlaneService) UpdateWorkspaceMemberRole(memberID string, role int) ([]byte, error) {
  1084. url := fmt.Sprintf("/api/workspaces/%s/members/%s/", s.WorkspaceSlug, memberID)
  1085. payload := map[string]interface{}{"role": role}
  1086. resp, err := s.Client.Patch(url, payload)
  1087. if err != nil {
  1088. return nil, err
  1089. }
  1090. if !IsSuccess(resp.StatusCode) {
  1091. return nil, fmt.Errorf("UpdateWorkspaceMemberRole 失败: %s, body=%s", resp.Status, string(resp.Body))
  1092. }
  1093. return resp.Body, nil
  1094. }
  1095. func (s *PlaneService) RemoveWorkspaceMember(memberID string) error {
  1096. url := fmt.Sprintf("/api/workspaces/%s/members/%s/", s.WorkspaceSlug, memberID)
  1097. resp, err := s.Client.Delete(url)
  1098. if err != nil {
  1099. return err
  1100. }
  1101. if resp.StatusCode != 204 {
  1102. return fmt.Errorf("RemoveWorkspaceMember 失败: %s", resp.Status)
  1103. }
  1104. return nil
  1105. }
  1106. func (s *PlaneService) ListProjectMembers() ([]byte, error) {
  1107. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/members/", s.WorkspaceSlug, s.ProjectId)
  1108. resp, err := s.Client.Get(url)
  1109. if err != nil {
  1110. return nil, err
  1111. }
  1112. if !IsSuccess(resp.StatusCode) {
  1113. return nil, fmt.Errorf("ListProjectMembers 失败: %s", resp.Status)
  1114. }
  1115. return resp.Body, nil
  1116. }
  1117. func (s *PlaneService) GetProjectMember(memberID string) ([]byte, error) {
  1118. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/members/%s/", s.WorkspaceSlug, s.ProjectId, memberID)
  1119. resp, err := s.Client.Get(url)
  1120. if err != nil {
  1121. return nil, err
  1122. }
  1123. if !IsSuccess(resp.StatusCode) {
  1124. return nil, fmt.Errorf("GetProjectMember 失败: %s", resp.Status)
  1125. }
  1126. return resp.Body, nil
  1127. }
  1128. func (s *PlaneService) AddProjectMember(memberID string, role int) ([]byte, error) {
  1129. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/members/", s.WorkspaceSlug, s.ProjectId)
  1130. payload := map[string]interface{}{"member": memberID, "role": role}
  1131. resp, err := s.Client.Post(url, payload)
  1132. if err != nil {
  1133. return nil, err
  1134. }
  1135. if !IsSuccess(resp.StatusCode) {
  1136. return nil, fmt.Errorf("AddProjectMember 失败: %s, body=%s", resp.Status, string(resp.Body))
  1137. }
  1138. return resp.Body, nil
  1139. }
  1140. func (s *PlaneService) UpdateProjectMemberRole(memberID string, role int) ([]byte, error) {
  1141. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/members/%s/", s.WorkspaceSlug, s.ProjectId, memberID)
  1142. payload := map[string]interface{}{"role": role}
  1143. resp, err := s.Client.Patch(url, payload)
  1144. if err != nil {
  1145. return nil, err
  1146. }
  1147. if !IsSuccess(resp.StatusCode) {
  1148. return nil, fmt.Errorf("UpdateProjectMemberRole 失败: %s, body=%s", resp.Status, string(resp.Body))
  1149. }
  1150. return resp.Body, nil
  1151. }
  1152. func (s *PlaneService) RemoveProjectMember(memberID string) error {
  1153. url := fmt.Sprintf("/api/workspaces/%s/projects/%s/members/%s/", s.WorkspaceSlug, s.ProjectId, memberID)
  1154. resp, err := s.Client.Delete(url)
  1155. if err != nil {
  1156. return err
  1157. }
  1158. if resp.StatusCode != 204 {
  1159. return fmt.Errorf("RemoveProjectMember 失败: %s", resp.Status)
  1160. }
  1161. return nil
  1162. }