gitea源码

issue_label.go 9.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. // Copyright 2016 The Gogs Authors. All rights reserved.
  2. // Copyright 2018 The Gitea Authors. All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package repo
  5. import (
  6. "errors"
  7. "net/http"
  8. "reflect"
  9. issues_model "code.gitea.io/gitea/models/issues"
  10. api "code.gitea.io/gitea/modules/structs"
  11. "code.gitea.io/gitea/modules/web"
  12. "code.gitea.io/gitea/services/context"
  13. "code.gitea.io/gitea/services/convert"
  14. issue_service "code.gitea.io/gitea/services/issue"
  15. )
  16. // ListIssueLabels list all the labels of an issue
  17. func ListIssueLabels(ctx *context.APIContext) {
  18. // swagger:operation GET /repos/{owner}/{repo}/issues/{index}/labels issue issueGetLabels
  19. // ---
  20. // summary: Get an issue's labels
  21. // produces:
  22. // - application/json
  23. // parameters:
  24. // - name: owner
  25. // in: path
  26. // description: owner of the repo
  27. // type: string
  28. // required: true
  29. // - name: repo
  30. // in: path
  31. // description: name of the repo
  32. // type: string
  33. // required: true
  34. // - name: index
  35. // in: path
  36. // description: index of the issue
  37. // type: integer
  38. // format: int64
  39. // required: true
  40. // responses:
  41. // "200":
  42. // "$ref": "#/responses/LabelList"
  43. // "404":
  44. // "$ref": "#/responses/notFound"
  45. issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
  46. if err != nil {
  47. if issues_model.IsErrIssueNotExist(err) {
  48. ctx.APIErrorNotFound()
  49. } else {
  50. ctx.APIErrorInternal(err)
  51. }
  52. return
  53. }
  54. if err := issue.LoadAttributes(ctx); err != nil {
  55. ctx.APIErrorInternal(err)
  56. return
  57. }
  58. ctx.JSON(http.StatusOK, convert.ToLabelList(issue.Labels, ctx.Repo.Repository, ctx.Repo.Owner))
  59. }
  60. // AddIssueLabels add labels for an issue
  61. func AddIssueLabels(ctx *context.APIContext) {
  62. // swagger:operation POST /repos/{owner}/{repo}/issues/{index}/labels issue issueAddLabel
  63. // ---
  64. // summary: Add a label to an issue
  65. // consumes:
  66. // - application/json
  67. // produces:
  68. // - application/json
  69. // parameters:
  70. // - name: owner
  71. // in: path
  72. // description: owner of the repo
  73. // type: string
  74. // required: true
  75. // - name: repo
  76. // in: path
  77. // description: name of the repo
  78. // type: string
  79. // required: true
  80. // - name: index
  81. // in: path
  82. // description: index of the issue
  83. // type: integer
  84. // format: int64
  85. // required: true
  86. // - name: body
  87. // in: body
  88. // schema:
  89. // "$ref": "#/definitions/IssueLabelsOption"
  90. // responses:
  91. // "200":
  92. // "$ref": "#/responses/LabelList"
  93. // "403":
  94. // "$ref": "#/responses/forbidden"
  95. // "404":
  96. // "$ref": "#/responses/notFound"
  97. form := web.GetForm(ctx).(*api.IssueLabelsOption)
  98. issue, labels, err := prepareForReplaceOrAdd(ctx, *form)
  99. if err != nil {
  100. return
  101. }
  102. if err = issue_service.AddLabels(ctx, issue, ctx.Doer, labels); err != nil {
  103. ctx.APIErrorInternal(err)
  104. return
  105. }
  106. labels, err = issues_model.GetLabelsByIssueID(ctx, issue.ID)
  107. if err != nil {
  108. ctx.APIErrorInternal(err)
  109. return
  110. }
  111. ctx.JSON(http.StatusOK, convert.ToLabelList(labels, ctx.Repo.Repository, ctx.Repo.Owner))
  112. }
  113. // DeleteIssueLabel delete a label for an issue
  114. func DeleteIssueLabel(ctx *context.APIContext) {
  115. // swagger:operation DELETE /repos/{owner}/{repo}/issues/{index}/labels/{id} issue issueRemoveLabel
  116. // ---
  117. // summary: Remove a label from an issue
  118. // produces:
  119. // - application/json
  120. // parameters:
  121. // - name: owner
  122. // in: path
  123. // description: owner of the repo
  124. // type: string
  125. // required: true
  126. // - name: repo
  127. // in: path
  128. // description: name of the repo
  129. // type: string
  130. // required: true
  131. // - name: index
  132. // in: path
  133. // description: index of the issue
  134. // type: integer
  135. // format: int64
  136. // required: true
  137. // - name: id
  138. // in: path
  139. // description: id of the label to remove
  140. // type: integer
  141. // format: int64
  142. // required: true
  143. // responses:
  144. // "204":
  145. // "$ref": "#/responses/empty"
  146. // "403":
  147. // "$ref": "#/responses/forbidden"
  148. // "404":
  149. // "$ref": "#/responses/notFound"
  150. // "422":
  151. // "$ref": "#/responses/validationError"
  152. issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
  153. if err != nil {
  154. if issues_model.IsErrIssueNotExist(err) {
  155. ctx.APIErrorNotFound()
  156. } else {
  157. ctx.APIErrorInternal(err)
  158. }
  159. return
  160. }
  161. if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
  162. ctx.Status(http.StatusForbidden)
  163. return
  164. }
  165. label, err := issues_model.GetLabelByID(ctx, ctx.PathParamInt64("id"))
  166. if err != nil {
  167. if issues_model.IsErrLabelNotExist(err) {
  168. ctx.APIError(http.StatusUnprocessableEntity, err)
  169. } else {
  170. ctx.APIErrorInternal(err)
  171. }
  172. return
  173. }
  174. if err := issue_service.RemoveLabel(ctx, issue, ctx.Doer, label); err != nil {
  175. ctx.APIErrorInternal(err)
  176. return
  177. }
  178. ctx.Status(http.StatusNoContent)
  179. }
  180. // ReplaceIssueLabels replace labels for an issue
  181. func ReplaceIssueLabels(ctx *context.APIContext) {
  182. // swagger:operation PUT /repos/{owner}/{repo}/issues/{index}/labels issue issueReplaceLabels
  183. // ---
  184. // summary: Replace an issue's labels
  185. // consumes:
  186. // - application/json
  187. // produces:
  188. // - application/json
  189. // parameters:
  190. // - name: owner
  191. // in: path
  192. // description: owner of the repo
  193. // type: string
  194. // required: true
  195. // - name: repo
  196. // in: path
  197. // description: name of the repo
  198. // type: string
  199. // required: true
  200. // - name: index
  201. // in: path
  202. // description: index of the issue
  203. // type: integer
  204. // format: int64
  205. // required: true
  206. // - name: body
  207. // in: body
  208. // schema:
  209. // "$ref": "#/definitions/IssueLabelsOption"
  210. // responses:
  211. // "200":
  212. // "$ref": "#/responses/LabelList"
  213. // "403":
  214. // "$ref": "#/responses/forbidden"
  215. // "404":
  216. // "$ref": "#/responses/notFound"
  217. form := web.GetForm(ctx).(*api.IssueLabelsOption)
  218. issue, labels, err := prepareForReplaceOrAdd(ctx, *form)
  219. if err != nil {
  220. return
  221. }
  222. if err := issue_service.ReplaceLabels(ctx, issue, ctx.Doer, labels); err != nil {
  223. ctx.APIErrorInternal(err)
  224. return
  225. }
  226. labels, err = issues_model.GetLabelsByIssueID(ctx, issue.ID)
  227. if err != nil {
  228. ctx.APIErrorInternal(err)
  229. return
  230. }
  231. ctx.JSON(http.StatusOK, convert.ToLabelList(labels, ctx.Repo.Repository, ctx.Repo.Owner))
  232. }
  233. // ClearIssueLabels delete all the labels for an issue
  234. func ClearIssueLabels(ctx *context.APIContext) {
  235. // swagger:operation DELETE /repos/{owner}/{repo}/issues/{index}/labels issue issueClearLabels
  236. // ---
  237. // summary: Remove all labels from an issue
  238. // produces:
  239. // - application/json
  240. // parameters:
  241. // - name: owner
  242. // in: path
  243. // description: owner of the repo
  244. // type: string
  245. // required: true
  246. // - name: repo
  247. // in: path
  248. // description: name of the repo
  249. // type: string
  250. // required: true
  251. // - name: index
  252. // in: path
  253. // description: index of the issue
  254. // type: integer
  255. // format: int64
  256. // required: true
  257. // responses:
  258. // "204":
  259. // "$ref": "#/responses/empty"
  260. // "403":
  261. // "$ref": "#/responses/forbidden"
  262. // "404":
  263. // "$ref": "#/responses/notFound"
  264. issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
  265. if err != nil {
  266. if issues_model.IsErrIssueNotExist(err) {
  267. ctx.APIErrorNotFound()
  268. } else {
  269. ctx.APIErrorInternal(err)
  270. }
  271. return
  272. }
  273. if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
  274. ctx.Status(http.StatusForbidden)
  275. return
  276. }
  277. if err := issue_service.ClearLabels(ctx, issue, ctx.Doer); err != nil {
  278. ctx.APIErrorInternal(err)
  279. return
  280. }
  281. ctx.Status(http.StatusNoContent)
  282. }
  283. func prepareForReplaceOrAdd(ctx *context.APIContext, form api.IssueLabelsOption) (*issues_model.Issue, []*issues_model.Label, error) {
  284. issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
  285. if err != nil {
  286. if issues_model.IsErrIssueNotExist(err) {
  287. ctx.APIErrorNotFound()
  288. } else {
  289. ctx.APIErrorInternal(err)
  290. }
  291. return nil, nil, err
  292. }
  293. if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
  294. ctx.APIError(http.StatusForbidden, "write permission is required")
  295. return nil, nil, errors.New("permission denied")
  296. }
  297. var (
  298. labelIDs []int64
  299. labelNames []string
  300. )
  301. for _, label := range form.Labels {
  302. rv := reflect.ValueOf(label)
  303. switch rv.Kind() {
  304. case reflect.Float64:
  305. labelIDs = append(labelIDs, int64(rv.Float()))
  306. case reflect.String:
  307. labelNames = append(labelNames, rv.String())
  308. default:
  309. ctx.APIError(http.StatusBadRequest, "a label must be an integer or a string")
  310. return nil, nil, errors.New("invalid label")
  311. }
  312. }
  313. if len(labelIDs) > 0 && len(labelNames) > 0 {
  314. ctx.APIError(http.StatusBadRequest, "labels should be an array of strings or integers")
  315. return nil, nil, errors.New("invalid labels")
  316. }
  317. if len(labelNames) > 0 {
  318. repoLabelIDs, err := issues_model.GetLabelIDsInRepoByNames(ctx, ctx.Repo.Repository.ID, labelNames)
  319. if err != nil {
  320. ctx.APIErrorInternal(err)
  321. return nil, nil, err
  322. }
  323. labelIDs = append(labelIDs, repoLabelIDs...)
  324. if ctx.Repo.Owner.IsOrganization() {
  325. orgLabelIDs, err := issues_model.GetLabelIDsInOrgByNames(ctx, ctx.Repo.Owner.ID, labelNames)
  326. if err != nil {
  327. ctx.APIErrorInternal(err)
  328. return nil, nil, err
  329. }
  330. labelIDs = append(labelIDs, orgLabelIDs...)
  331. }
  332. }
  333. labels, err := issues_model.GetLabelsByIDs(ctx, labelIDs, "id", "repo_id", "org_id", "name", "exclusive")
  334. if err != nil {
  335. ctx.APIErrorInternal(err)
  336. return nil, nil, err
  337. }
  338. return issue, labels, err
  339. }