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.

milestone.go 8.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. // Copyright 2016 The Gogs Authors. All rights reserved.
  2. // Copyright 2020 The Gitea Authors.
  3. // SPDX-License-Identifier: MIT
  4. package repo
  5. import (
  6. "net/http"
  7. "strconv"
  8. "time"
  9. "code.gitea.io/gitea/models/db"
  10. issues_model "code.gitea.io/gitea/models/issues"
  11. "code.gitea.io/gitea/modules/optional"
  12. api "code.gitea.io/gitea/modules/structs"
  13. "code.gitea.io/gitea/modules/timeutil"
  14. "code.gitea.io/gitea/modules/web"
  15. "code.gitea.io/gitea/routers/api/v1/utils"
  16. "code.gitea.io/gitea/services/context"
  17. "code.gitea.io/gitea/services/convert"
  18. )
  19. // ListMilestones list milestones for a repository
  20. func ListMilestones(ctx *context.APIContext) {
  21. // swagger:operation GET /repos/{owner}/{repo}/milestones issue issueGetMilestonesList
  22. // ---
  23. // summary: Get all of a repository's opened milestones
  24. // produces:
  25. // - application/json
  26. // parameters:
  27. // - name: owner
  28. // in: path
  29. // description: owner of the repo
  30. // type: string
  31. // required: true
  32. // - name: repo
  33. // in: path
  34. // description: name of the repo
  35. // type: string
  36. // required: true
  37. // - name: state
  38. // in: query
  39. // description: Milestone state, Recognized values are open, closed and all. Defaults to "open"
  40. // type: string
  41. // - name: name
  42. // in: query
  43. // description: filter by milestone name
  44. // type: string
  45. // - name: page
  46. // in: query
  47. // description: page number of results to return (1-based)
  48. // type: integer
  49. // - name: limit
  50. // in: query
  51. // description: page size of results
  52. // type: integer
  53. // responses:
  54. // "200":
  55. // "$ref": "#/responses/MilestoneList"
  56. // "404":
  57. // "$ref": "#/responses/notFound"
  58. state := api.StateType(ctx.FormString("state"))
  59. var isClosed optional.Option[bool]
  60. switch state {
  61. case api.StateClosed, api.StateOpen:
  62. isClosed = optional.Some(state == api.StateClosed)
  63. }
  64. milestones, total, err := db.FindAndCount[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{
  65. ListOptions: utils.GetListOptions(ctx),
  66. RepoID: ctx.Repo.Repository.ID,
  67. IsClosed: isClosed,
  68. Name: ctx.FormString("name"),
  69. })
  70. if err != nil {
  71. ctx.Error(http.StatusInternalServerError, "db.FindAndCount[issues_model.Milestone]", err)
  72. return
  73. }
  74. apiMilestones := make([]*api.Milestone, len(milestones))
  75. for i := range milestones {
  76. apiMilestones[i] = convert.ToAPIMilestone(milestones[i])
  77. }
  78. ctx.SetTotalCountHeader(total)
  79. ctx.JSON(http.StatusOK, &apiMilestones)
  80. }
  81. // GetMilestone get a milestone for a repository by ID and if not available by name
  82. func GetMilestone(ctx *context.APIContext) {
  83. // swagger:operation GET /repos/{owner}/{repo}/milestones/{id} issue issueGetMilestone
  84. // ---
  85. // summary: Get a milestone
  86. // produces:
  87. // - application/json
  88. // parameters:
  89. // - name: owner
  90. // in: path
  91. // description: owner of the repo
  92. // type: string
  93. // required: true
  94. // - name: repo
  95. // in: path
  96. // description: name of the repo
  97. // type: string
  98. // required: true
  99. // - name: id
  100. // in: path
  101. // description: the milestone to get, identified by ID and if not available by name
  102. // type: string
  103. // required: true
  104. // responses:
  105. // "200":
  106. // "$ref": "#/responses/Milestone"
  107. // "404":
  108. // "$ref": "#/responses/notFound"
  109. milestone := getMilestoneByIDOrName(ctx)
  110. if ctx.Written() {
  111. return
  112. }
  113. ctx.JSON(http.StatusOK, convert.ToAPIMilestone(milestone))
  114. }
  115. // CreateMilestone create a milestone for a repository
  116. func CreateMilestone(ctx *context.APIContext) {
  117. // swagger:operation POST /repos/{owner}/{repo}/milestones issue issueCreateMilestone
  118. // ---
  119. // summary: Create a milestone
  120. // consumes:
  121. // - application/json
  122. // produces:
  123. // - application/json
  124. // parameters:
  125. // - name: owner
  126. // in: path
  127. // description: owner of the repo
  128. // type: string
  129. // required: true
  130. // - name: repo
  131. // in: path
  132. // description: name of the repo
  133. // type: string
  134. // required: true
  135. // - name: body
  136. // in: body
  137. // schema:
  138. // "$ref": "#/definitions/CreateMilestoneOption"
  139. // responses:
  140. // "201":
  141. // "$ref": "#/responses/Milestone"
  142. // "404":
  143. // "$ref": "#/responses/notFound"
  144. form := web.GetForm(ctx).(*api.CreateMilestoneOption)
  145. if form.Deadline == nil {
  146. defaultDeadline, _ := time.ParseInLocation("2006-01-02", "9999-12-31", time.Local)
  147. form.Deadline = &defaultDeadline
  148. }
  149. milestone := &issues_model.Milestone{
  150. RepoID: ctx.Repo.Repository.ID,
  151. Name: form.Title,
  152. Content: form.Description,
  153. DeadlineUnix: timeutil.TimeStamp(form.Deadline.Unix()),
  154. }
  155. if form.State == "closed" {
  156. milestone.IsClosed = true
  157. milestone.ClosedDateUnix = timeutil.TimeStampNow()
  158. }
  159. if err := issues_model.NewMilestone(ctx, milestone); err != nil {
  160. ctx.Error(http.StatusInternalServerError, "NewMilestone", err)
  161. return
  162. }
  163. ctx.JSON(http.StatusCreated, convert.ToAPIMilestone(milestone))
  164. }
  165. // EditMilestone modify a milestone for a repository by ID and if not available by name
  166. func EditMilestone(ctx *context.APIContext) {
  167. // swagger:operation PATCH /repos/{owner}/{repo}/milestones/{id} issue issueEditMilestone
  168. // ---
  169. // summary: Update a milestone
  170. // consumes:
  171. // - application/json
  172. // produces:
  173. // - application/json
  174. // parameters:
  175. // - name: owner
  176. // in: path
  177. // description: owner of the repo
  178. // type: string
  179. // required: true
  180. // - name: repo
  181. // in: path
  182. // description: name of the repo
  183. // type: string
  184. // required: true
  185. // - name: id
  186. // in: path
  187. // description: the milestone to edit, identified by ID and if not available by name
  188. // type: string
  189. // required: true
  190. // - name: body
  191. // in: body
  192. // schema:
  193. // "$ref": "#/definitions/EditMilestoneOption"
  194. // responses:
  195. // "200":
  196. // "$ref": "#/responses/Milestone"
  197. // "404":
  198. // "$ref": "#/responses/notFound"
  199. form := web.GetForm(ctx).(*api.EditMilestoneOption)
  200. milestone := getMilestoneByIDOrName(ctx)
  201. if ctx.Written() {
  202. return
  203. }
  204. if len(form.Title) > 0 {
  205. milestone.Name = form.Title
  206. }
  207. if form.Description != nil {
  208. milestone.Content = *form.Description
  209. }
  210. if form.Deadline != nil && !form.Deadline.IsZero() {
  211. milestone.DeadlineUnix = timeutil.TimeStamp(form.Deadline.Unix())
  212. }
  213. oldIsClosed := milestone.IsClosed
  214. if form.State != nil {
  215. milestone.IsClosed = *form.State == string(api.StateClosed)
  216. }
  217. if err := issues_model.UpdateMilestone(ctx, milestone, oldIsClosed); err != nil {
  218. ctx.Error(http.StatusInternalServerError, "UpdateMilestone", err)
  219. return
  220. }
  221. ctx.JSON(http.StatusOK, convert.ToAPIMilestone(milestone))
  222. }
  223. // DeleteMilestone delete a milestone for a repository by ID and if not available by name
  224. func DeleteMilestone(ctx *context.APIContext) {
  225. // swagger:operation DELETE /repos/{owner}/{repo}/milestones/{id} issue issueDeleteMilestone
  226. // ---
  227. // summary: Delete a milestone
  228. // parameters:
  229. // - name: owner
  230. // in: path
  231. // description: owner of the repo
  232. // type: string
  233. // required: true
  234. // - name: repo
  235. // in: path
  236. // description: name of the repo
  237. // type: string
  238. // required: true
  239. // - name: id
  240. // in: path
  241. // description: the milestone to delete, identified by ID and if not available by name
  242. // type: string
  243. // required: true
  244. // responses:
  245. // "204":
  246. // "$ref": "#/responses/empty"
  247. // "404":
  248. // "$ref": "#/responses/notFound"
  249. m := getMilestoneByIDOrName(ctx)
  250. if ctx.Written() {
  251. return
  252. }
  253. if err := issues_model.DeleteMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, m.ID); err != nil {
  254. ctx.Error(http.StatusInternalServerError, "DeleteMilestoneByRepoID", err)
  255. return
  256. }
  257. ctx.Status(http.StatusNoContent)
  258. }
  259. // getMilestoneByIDOrName get milestone by ID and if not available by name
  260. func getMilestoneByIDOrName(ctx *context.APIContext) *issues_model.Milestone {
  261. mile := ctx.Params(":id")
  262. mileID, _ := strconv.ParseInt(mile, 0, 64)
  263. if mileID != 0 {
  264. milestone, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, mileID)
  265. if err == nil {
  266. return milestone
  267. } else if !issues_model.IsErrMilestoneNotExist(err) {
  268. ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err)
  269. return nil
  270. }
  271. }
  272. milestone, err := issues_model.GetMilestoneByRepoIDANDName(ctx, ctx.Repo.Repository.ID, mile)
  273. if err != nil {
  274. if issues_model.IsErrMilestoneNotExist(err) {
  275. ctx.NotFound()
  276. return nil
  277. }
  278. ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err)
  279. return nil
  280. }
  281. return milestone
  282. }