gitea源码

issue_reaction.go 12KB

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