You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

issue.go 7.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. // Copyright 2016 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package repo
  5. import (
  6. "fmt"
  7. "strings"
  8. api "code.gitea.io/sdk/gitea"
  9. "code.gitea.io/gitea/models"
  10. "code.gitea.io/gitea/modules/context"
  11. "code.gitea.io/gitea/modules/indexer"
  12. "code.gitea.io/gitea/modules/setting"
  13. "code.gitea.io/gitea/modules/util"
  14. )
  15. // ListIssues list the issues of a repository
  16. func ListIssues(ctx *context.APIContext) {
  17. // swagger:operation GET /repos/{owner}/{repo}/issues issue issueListIssues
  18. // ---
  19. // summary: List a repository's issues
  20. // produces:
  21. // - application/json
  22. // parameters:
  23. // - name: owner
  24. // in: path
  25. // description: owner of the repo
  26. // type: string
  27. // required: true
  28. // - name: repo
  29. // in: path
  30. // description: name of the repo
  31. // type: string
  32. // required: true
  33. // - name: state
  34. // in: query
  35. // description: whether issue is open or closed
  36. // type: string
  37. // - name: page
  38. // in: query
  39. // description: page number of requested issues
  40. // type: integer
  41. // - name: q
  42. // in: query
  43. // description: search string
  44. // type: string
  45. // responses:
  46. // "200":
  47. // "$ref": "#/responses/IssueList"
  48. var isClosed util.OptionalBool
  49. switch ctx.Query("state") {
  50. case "closed":
  51. isClosed = util.OptionalBoolTrue
  52. case "all":
  53. isClosed = util.OptionalBoolNone
  54. default:
  55. isClosed = util.OptionalBoolFalse
  56. }
  57. var issues []*models.Issue
  58. keyword := strings.Trim(ctx.Query("q"), " ")
  59. if strings.IndexByte(keyword, 0) >= 0 {
  60. keyword = ""
  61. }
  62. var issueIDs []int64
  63. var err error
  64. if len(keyword) > 0 {
  65. issueIDs, err = indexer.SearchIssuesByKeyword(ctx.Repo.Repository.ID, keyword)
  66. }
  67. // Only fetch the issues if we either don't have a keyword or the search returned issues
  68. // This would otherwise return all issues if no issues were found by the search.
  69. if len(keyword) == 0 || len(issueIDs) > 0 {
  70. issues, err = models.Issues(&models.IssuesOptions{
  71. RepoIDs: []int64{ctx.Repo.Repository.ID},
  72. Page: ctx.QueryInt("page"),
  73. PageSize: setting.UI.IssuePagingNum,
  74. IsClosed: isClosed,
  75. IssueIDs: issueIDs,
  76. })
  77. }
  78. if err != nil {
  79. ctx.Error(500, "Issues", err)
  80. return
  81. }
  82. apiIssues := make([]*api.Issue, len(issues))
  83. for i := range issues {
  84. apiIssues[i] = issues[i].APIFormat()
  85. }
  86. ctx.SetLinkHeader(ctx.Repo.Repository.NumIssues, setting.UI.IssuePagingNum)
  87. ctx.JSON(200, &apiIssues)
  88. }
  89. // GetIssue get an issue of a repository
  90. func GetIssue(ctx *context.APIContext) {
  91. // swagger:operation GET /repos/{owner}/{repo}/issues/{index} issue issueGetIssue
  92. // ---
  93. // summary: Get an issue
  94. // produces:
  95. // - application/json
  96. // parameters:
  97. // - name: owner
  98. // in: path
  99. // description: owner of the repo
  100. // type: string
  101. // required: true
  102. // - name: repo
  103. // in: path
  104. // description: name of the repo
  105. // type: string
  106. // required: true
  107. // - name: index
  108. // in: path
  109. // description: index of the issue to get
  110. // type: integer
  111. // required: true
  112. // responses:
  113. // "200":
  114. // "$ref": "#/responses/Issue"
  115. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  116. if err != nil {
  117. if models.IsErrIssueNotExist(err) {
  118. ctx.Status(404)
  119. } else {
  120. ctx.Error(500, "GetIssueByIndex", err)
  121. }
  122. return
  123. }
  124. ctx.JSON(200, issue.APIFormat())
  125. }
  126. // CreateIssue create an issue of a repository
  127. func CreateIssue(ctx *context.APIContext, form api.CreateIssueOption) {
  128. // swagger:operation POST /repos/{owner}/{repo}/issues issue issueCreateIssue
  129. // ---
  130. // summary: Create an issue
  131. // consumes:
  132. // - application/json
  133. // produces:
  134. // - application/json
  135. // parameters:
  136. // - name: owner
  137. // in: path
  138. // description: owner of the repo
  139. // type: string
  140. // required: true
  141. // - name: repo
  142. // in: path
  143. // description: name of the repo
  144. // type: string
  145. // required: true
  146. // - name: body
  147. // in: body
  148. // schema:
  149. // "$ref": "#/definitions/CreateIssueOption"
  150. // responses:
  151. // "201":
  152. // "$ref": "#/responses/Issue"
  153. issue := &models.Issue{
  154. RepoID: ctx.Repo.Repository.ID,
  155. Title: form.Title,
  156. PosterID: ctx.User.ID,
  157. Poster: ctx.User,
  158. Content: form.Body,
  159. }
  160. if ctx.Repo.IsWriter() {
  161. if len(form.Assignee) > 0 {
  162. assignee, err := models.GetUserByName(form.Assignee)
  163. if err != nil {
  164. if models.IsErrUserNotExist(err) {
  165. ctx.Error(422, "", fmt.Sprintf("Assignee does not exist: [name: %s]", form.Assignee))
  166. } else {
  167. ctx.Error(500, "GetUserByName", err)
  168. }
  169. return
  170. }
  171. issue.AssigneeID = assignee.ID
  172. }
  173. issue.MilestoneID = form.Milestone
  174. } else {
  175. form.Labels = nil
  176. }
  177. if err := models.NewIssue(ctx.Repo.Repository, issue, form.Labels, nil); err != nil {
  178. ctx.Error(500, "NewIssue", err)
  179. return
  180. }
  181. if form.Closed {
  182. if err := issue.ChangeStatus(ctx.User, ctx.Repo.Repository, true); err != nil {
  183. ctx.Error(500, "ChangeStatus", err)
  184. return
  185. }
  186. }
  187. // Refetch from database to assign some automatic values
  188. var err error
  189. issue, err = models.GetIssueByID(issue.ID)
  190. if err != nil {
  191. ctx.Error(500, "GetIssueByID", err)
  192. return
  193. }
  194. ctx.JSON(201, issue.APIFormat())
  195. }
  196. // EditIssue modify an issue of a repository
  197. func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
  198. // swagger:operation PATCH /repos/{owner}/{repo}/issues/{index} issue issueEditIssue
  199. // ---
  200. // summary: Edit an issue
  201. // consumes:
  202. // - application/json
  203. // produces:
  204. // - application/json
  205. // parameters:
  206. // - name: owner
  207. // in: path
  208. // description: owner of the repo
  209. // type: string
  210. // required: true
  211. // - name: repo
  212. // in: path
  213. // description: name of the repo
  214. // type: string
  215. // required: true
  216. // - name: index
  217. // in: path
  218. // description: index of the issue to edit
  219. // type: integer
  220. // required: true
  221. // - name: body
  222. // in: body
  223. // schema:
  224. // "$ref": "#/definitions/EditIssueOption"
  225. // responses:
  226. // "201":
  227. // "$ref": "#/responses/Issue"
  228. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  229. if err != nil {
  230. if models.IsErrIssueNotExist(err) {
  231. ctx.Status(404)
  232. } else {
  233. ctx.Error(500, "GetIssueByIndex", err)
  234. }
  235. return
  236. }
  237. if !issue.IsPoster(ctx.User.ID) && !ctx.Repo.IsWriter() {
  238. ctx.Status(403)
  239. return
  240. }
  241. if len(form.Title) > 0 {
  242. issue.Title = form.Title
  243. }
  244. if form.Body != nil {
  245. issue.Content = *form.Body
  246. }
  247. if ctx.Repo.IsWriter() && form.Assignee != nil &&
  248. (issue.Assignee == nil || issue.Assignee.LowerName != strings.ToLower(*form.Assignee)) {
  249. if len(*form.Assignee) == 0 {
  250. issue.AssigneeID = 0
  251. } else {
  252. assignee, err := models.GetUserByName(*form.Assignee)
  253. if err != nil {
  254. if models.IsErrUserNotExist(err) {
  255. ctx.Error(422, "", fmt.Sprintf("assignee does not exist: [name: %s]", *form.Assignee))
  256. } else {
  257. ctx.Error(500, "GetUserByName", err)
  258. }
  259. return
  260. }
  261. issue.AssigneeID = assignee.ID
  262. }
  263. if err = models.UpdateIssueUserByAssignee(issue); err != nil {
  264. ctx.Error(500, "UpdateIssueUserByAssignee", err)
  265. return
  266. }
  267. }
  268. if ctx.Repo.IsWriter() && form.Milestone != nil &&
  269. issue.MilestoneID != *form.Milestone {
  270. oldMilestoneID := issue.MilestoneID
  271. issue.MilestoneID = *form.Milestone
  272. if err = models.ChangeMilestoneAssign(issue, ctx.User, oldMilestoneID); err != nil {
  273. ctx.Error(500, "ChangeMilestoneAssign", err)
  274. return
  275. }
  276. }
  277. if err = models.UpdateIssue(issue); err != nil {
  278. ctx.Error(500, "UpdateIssue", err)
  279. return
  280. }
  281. if form.State != nil {
  282. if err = issue.ChangeStatus(ctx.User, ctx.Repo.Repository, api.StateClosed == api.StateType(*form.State)); err != nil {
  283. ctx.Error(500, "ChangeStatus", err)
  284. return
  285. }
  286. }
  287. // Refetch from database to assign some automatic values
  288. issue, err = models.GetIssueByID(issue.ID)
  289. if err != nil {
  290. ctx.Error(500, "GetIssueByID", err)
  291. return
  292. }
  293. ctx.JSON(201, issue.APIFormat())
  294. }