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_reaction.go 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. // Copyright 2019 The Gitea 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. "errors"
  7. "net/http"
  8. "code.gitea.io/gitea/models"
  9. "code.gitea.io/gitea/modules/context"
  10. api "code.gitea.io/gitea/modules/structs"
  11. "code.gitea.io/gitea/routers/api/v1/utils"
  12. )
  13. // GetIssueCommentReactions list reactions of a comment from an issue
  14. func GetIssueCommentReactions(ctx *context.APIContext) {
  15. // swagger:operation GET /repos/{owner}/{repo}/issues/comments/{id}/reactions issue issueGetCommentReactions
  16. // ---
  17. // summary: Get a list of reactions from a comment of an issue
  18. // consumes:
  19. // - application/json
  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: id
  34. // in: path
  35. // description: id of the comment to edit
  36. // type: integer
  37. // format: int64
  38. // required: true
  39. // responses:
  40. // "200":
  41. // "$ref": "#/responses/ReactionList"
  42. // "403":
  43. // "$ref": "#/responses/forbidden"
  44. comment, err := models.GetCommentByID(ctx.ParamsInt64(":id"))
  45. if err != nil {
  46. if models.IsErrCommentNotExist(err) {
  47. ctx.NotFound(err)
  48. } else {
  49. ctx.Error(http.StatusInternalServerError, "GetCommentByID", err)
  50. }
  51. return
  52. }
  53. if !ctx.Repo.CanRead(models.UnitTypeIssues) {
  54. ctx.Error(http.StatusForbidden, "GetIssueCommentReactions", errors.New("no permission to get reactions"))
  55. return
  56. }
  57. reactions, err := models.FindCommentReactions(comment)
  58. if err != nil {
  59. ctx.Error(http.StatusInternalServerError, "FindIssueReactions", err)
  60. return
  61. }
  62. _, err = reactions.LoadUsers(ctx.Repo.Repository)
  63. if err != nil {
  64. ctx.Error(http.StatusInternalServerError, "ReactionList.LoadUsers()", err)
  65. return
  66. }
  67. var result []api.Reaction
  68. for _, r := range reactions {
  69. result = append(result, api.Reaction{
  70. User: r.User.APIFormat(),
  71. Reaction: r.Type,
  72. Created: r.CreatedUnix.AsTime(),
  73. })
  74. }
  75. ctx.JSON(http.StatusOK, result)
  76. }
  77. // PostIssueCommentReaction add a reaction to a comment of an issue
  78. func PostIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOption) {
  79. // swagger:operation POST /repos/{owner}/{repo}/issues/comments/{id}/reactions issue issuePostCommentReaction
  80. // ---
  81. // summary: Add a reaction to a comment of an issue
  82. // consumes:
  83. // - application/json
  84. // produces:
  85. // - application/json
  86. // parameters:
  87. // - name: owner
  88. // in: path
  89. // description: owner of the repo
  90. // type: string
  91. // required: true
  92. // - name: repo
  93. // in: path
  94. // description: name of the repo
  95. // type: string
  96. // required: true
  97. // - name: id
  98. // in: path
  99. // description: id of the comment to edit
  100. // type: integer
  101. // format: int64
  102. // required: true
  103. // - name: content
  104. // in: body
  105. // schema:
  106. // "$ref": "#/definitions/EditReactionOption"
  107. // responses:
  108. // "200":
  109. // "$ref": "#/responses/Reaction"
  110. // "201":
  111. // "$ref": "#/responses/Reaction"
  112. // "403":
  113. // "$ref": "#/responses/forbidden"
  114. changeIssueCommentReaction(ctx, form, true)
  115. }
  116. // DeleteIssueCommentReaction remove a reaction from a comment of an issue
  117. func DeleteIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOption) {
  118. // swagger:operation DELETE /repos/{owner}/{repo}/issues/comments/{id}/reactions issue issueDeleteCommentReaction
  119. // ---
  120. // summary: Remove a reaction from a comment of an issue
  121. // consumes:
  122. // - application/json
  123. // produces:
  124. // - application/json
  125. // parameters:
  126. // - name: owner
  127. // in: path
  128. // description: owner of the repo
  129. // type: string
  130. // required: true
  131. // - name: repo
  132. // in: path
  133. // description: name of the repo
  134. // type: string
  135. // required: true
  136. // - name: id
  137. // in: path
  138. // description: id of the comment to edit
  139. // type: integer
  140. // format: int64
  141. // required: true
  142. // - name: content
  143. // in: body
  144. // schema:
  145. // "$ref": "#/definitions/EditReactionOption"
  146. // responses:
  147. // "200":
  148. // "$ref": "#/responses/empty"
  149. // "403":
  150. // "$ref": "#/responses/forbidden"
  151. changeIssueCommentReaction(ctx, form, false)
  152. }
  153. func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOption, isCreateType bool) {
  154. comment, err := models.GetCommentByID(ctx.ParamsInt64(":id"))
  155. if err != nil {
  156. if models.IsErrCommentNotExist(err) {
  157. ctx.NotFound(err)
  158. } else {
  159. ctx.Error(http.StatusInternalServerError, "GetCommentByID", err)
  160. }
  161. return
  162. }
  163. err = comment.LoadIssue()
  164. if err != nil {
  165. ctx.Error(http.StatusInternalServerError, "comment.LoadIssue() failed", err)
  166. }
  167. if comment.Issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull) {
  168. ctx.Error(http.StatusForbidden, "ChangeIssueCommentReaction", errors.New("no permission to change reaction"))
  169. return
  170. }
  171. if isCreateType {
  172. // PostIssueCommentReaction part
  173. reaction, err := models.CreateCommentReaction(ctx.User, comment.Issue, comment, form.Reaction)
  174. if err != nil {
  175. if models.IsErrForbiddenIssueReaction(err) {
  176. ctx.Error(http.StatusForbidden, err.Error(), err)
  177. } else if models.IsErrReactionAlreadyExist(err) {
  178. ctx.JSON(http.StatusOK, api.Reaction{
  179. User: ctx.User.APIFormat(),
  180. Reaction: reaction.Type,
  181. Created: reaction.CreatedUnix.AsTime(),
  182. })
  183. } else {
  184. ctx.Error(http.StatusInternalServerError, "CreateCommentReaction", err)
  185. }
  186. return
  187. }
  188. ctx.JSON(http.StatusCreated, api.Reaction{
  189. User: ctx.User.APIFormat(),
  190. Reaction: reaction.Type,
  191. Created: reaction.CreatedUnix.AsTime(),
  192. })
  193. } else {
  194. // DeleteIssueCommentReaction part
  195. err = models.DeleteCommentReaction(ctx.User, comment.Issue, comment, form.Reaction)
  196. if err != nil {
  197. ctx.Error(http.StatusInternalServerError, "DeleteCommentReaction", err)
  198. return
  199. }
  200. //ToDo respond 204
  201. ctx.Status(http.StatusOK)
  202. }
  203. }
  204. // GetIssueReactions list reactions of an issue
  205. func GetIssueReactions(ctx *context.APIContext) {
  206. // swagger:operation GET /repos/{owner}/{repo}/issues/{index}/reactions issue issueGetIssueReactions
  207. // ---
  208. // summary: Get a list reactions of an issue
  209. // consumes:
  210. // - application/json
  211. // produces:
  212. // - application/json
  213. // parameters:
  214. // - name: owner
  215. // in: path
  216. // description: owner of the repo
  217. // type: string
  218. // required: true
  219. // - name: repo
  220. // in: path
  221. // description: name of the repo
  222. // type: string
  223. // required: true
  224. // - name: index
  225. // in: path
  226. // description: index of the issue
  227. // type: integer
  228. // format: int64
  229. // required: true
  230. // - name: page
  231. // in: query
  232. // description: page number of results to return (1-based)
  233. // type: integer
  234. // - name: limit
  235. // in: query
  236. // description: page size of results, maximum page size is 50
  237. // type: integer
  238. // responses:
  239. // "200":
  240. // "$ref": "#/responses/ReactionList"
  241. // "403":
  242. // "$ref": "#/responses/forbidden"
  243. issue, err := models.GetIssueWithAttrsByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  244. if err != nil {
  245. if models.IsErrIssueNotExist(err) {
  246. ctx.NotFound()
  247. } else {
  248. ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
  249. }
  250. return
  251. }
  252. if !ctx.Repo.CanRead(models.UnitTypeIssues) {
  253. ctx.Error(http.StatusForbidden, "GetIssueReactions", errors.New("no permission to get reactions"))
  254. return
  255. }
  256. reactions, err := models.FindIssueReactions(issue, utils.GetListOptions(ctx))
  257. if err != nil {
  258. ctx.Error(http.StatusInternalServerError, "FindIssueReactions", err)
  259. return
  260. }
  261. _, err = reactions.LoadUsers(ctx.Repo.Repository)
  262. if err != nil {
  263. ctx.Error(http.StatusInternalServerError, "ReactionList.LoadUsers()", err)
  264. return
  265. }
  266. var result []api.Reaction
  267. for _, r := range reactions {
  268. result = append(result, api.Reaction{
  269. User: r.User.APIFormat(),
  270. Reaction: r.Type,
  271. Created: r.CreatedUnix.AsTime(),
  272. })
  273. }
  274. ctx.JSON(http.StatusOK, result)
  275. }
  276. // PostIssueReaction add a reaction to an issue
  277. func PostIssueReaction(ctx *context.APIContext, form api.EditReactionOption) {
  278. // swagger:operation POST /repos/{owner}/{repo}/issues/{index}/reactions issue issuePostIssueReaction
  279. // ---
  280. // summary: Add a reaction to an issue
  281. // consumes:
  282. // - application/json
  283. // produces:
  284. // - application/json
  285. // parameters:
  286. // - name: owner
  287. // in: path
  288. // description: owner of the repo
  289. // type: string
  290. // required: true
  291. // - name: repo
  292. // in: path
  293. // description: name of the repo
  294. // type: string
  295. // required: true
  296. // - name: index
  297. // in: path
  298. // description: index of the issue
  299. // type: integer
  300. // format: int64
  301. // required: true
  302. // - name: content
  303. // in: body
  304. // schema:
  305. // "$ref": "#/definitions/EditReactionOption"
  306. // responses:
  307. // "200":
  308. // "$ref": "#/responses/Reaction"
  309. // "201":
  310. // "$ref": "#/responses/Reaction"
  311. // "403":
  312. // "$ref": "#/responses/forbidden"
  313. changeIssueReaction(ctx, form, true)
  314. }
  315. // DeleteIssueReaction remove a reaction from an issue
  316. func DeleteIssueReaction(ctx *context.APIContext, form api.EditReactionOption) {
  317. // swagger:operation DELETE /repos/{owner}/{repo}/issues/{index}/reactions issue issueDeleteIssueReaction
  318. // ---
  319. // summary: Remove a reaction from an issue
  320. // consumes:
  321. // - application/json
  322. // produces:
  323. // - application/json
  324. // parameters:
  325. // - name: owner
  326. // in: path
  327. // description: owner of the repo
  328. // type: string
  329. // required: true
  330. // - name: repo
  331. // in: path
  332. // description: name of the repo
  333. // type: string
  334. // required: true
  335. // - name: index
  336. // in: path
  337. // description: index of the issue
  338. // type: integer
  339. // format: int64
  340. // required: true
  341. // - name: content
  342. // in: body
  343. // schema:
  344. // "$ref": "#/definitions/EditReactionOption"
  345. // responses:
  346. // "200":
  347. // "$ref": "#/responses/empty"
  348. // "403":
  349. // "$ref": "#/responses/forbidden"
  350. changeIssueReaction(ctx, form, false)
  351. }
  352. func changeIssueReaction(ctx *context.APIContext, form api.EditReactionOption, isCreateType bool) {
  353. issue, err := models.GetIssueWithAttrsByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  354. if err != nil {
  355. if models.IsErrIssueNotExist(err) {
  356. ctx.NotFound()
  357. } else {
  358. ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
  359. }
  360. return
  361. }
  362. if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
  363. ctx.Error(http.StatusForbidden, "ChangeIssueCommentReaction", errors.New("no permission to change reaction"))
  364. return
  365. }
  366. if isCreateType {
  367. // PostIssueReaction part
  368. reaction, err := models.CreateIssueReaction(ctx.User, issue, form.Reaction)
  369. if err != nil {
  370. if models.IsErrForbiddenIssueReaction(err) {
  371. ctx.Error(http.StatusForbidden, err.Error(), err)
  372. } else if models.IsErrReactionAlreadyExist(err) {
  373. ctx.JSON(http.StatusOK, api.Reaction{
  374. User: ctx.User.APIFormat(),
  375. Reaction: reaction.Type,
  376. Created: reaction.CreatedUnix.AsTime(),
  377. })
  378. } else {
  379. ctx.Error(http.StatusInternalServerError, "CreateCommentReaction", err)
  380. }
  381. return
  382. }
  383. ctx.JSON(http.StatusCreated, api.Reaction{
  384. User: ctx.User.APIFormat(),
  385. Reaction: reaction.Type,
  386. Created: reaction.CreatedUnix.AsTime(),
  387. })
  388. } else {
  389. // DeleteIssueReaction part
  390. err = models.DeleteIssueReaction(ctx.User, issue, form.Reaction)
  391. if err != nil {
  392. ctx.Error(http.StatusInternalServerError, "DeleteIssueReaction", err)
  393. return
  394. }
  395. //ToDo respond 204
  396. ctx.Status(http.StatusOK)
  397. }
  398. }