service.go 42 KB

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