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_comment.go 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. // Copyright 2015 The Gogs Authors. All rights reserved.
  2. // Copyright 2020 The Gitea Authors.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package repo
  6. import (
  7. "errors"
  8. "net/http"
  9. "code.gitea.io/gitea/models"
  10. "code.gitea.io/gitea/modules/context"
  11. "code.gitea.io/gitea/modules/convert"
  12. api "code.gitea.io/gitea/modules/structs"
  13. "code.gitea.io/gitea/routers/api/v1/utils"
  14. comment_service "code.gitea.io/gitea/services/comments"
  15. )
  16. // ListIssueComments list all the comments of an issue
  17. func ListIssueComments(ctx *context.APIContext) {
  18. // swagger:operation GET /repos/{owner}/{repo}/issues/{index}/comments issue issueGetComments
  19. // ---
  20. // summary: List all comments on an issue
  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. // - name: since
  41. // in: query
  42. // description: if provided, only comments updated since the specified time are returned.
  43. // type: string
  44. // format: date-time
  45. // - name: before
  46. // in: query
  47. // description: if provided, only comments updated before the provided time are returned.
  48. // type: string
  49. // format: date-time
  50. // responses:
  51. // "200":
  52. // "$ref": "#/responses/CommentList"
  53. before, since, err := utils.GetQueryBeforeSince(ctx)
  54. if err != nil {
  55. ctx.Error(http.StatusInternalServerError, "GetQueryBeforeSince", err)
  56. return
  57. }
  58. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  59. if err != nil {
  60. ctx.Error(http.StatusInternalServerError, "GetRawIssueByIndex", err)
  61. return
  62. }
  63. issue.Repo = ctx.Repo.Repository
  64. comments, err := models.FindComments(models.FindCommentsOptions{
  65. IssueID: issue.ID,
  66. Since: since,
  67. Before: before,
  68. Type: models.CommentTypeComment,
  69. })
  70. if err != nil {
  71. ctx.Error(http.StatusInternalServerError, "FindComments", err)
  72. return
  73. }
  74. if err := models.CommentList(comments).LoadPosters(); err != nil {
  75. ctx.Error(http.StatusInternalServerError, "LoadPosters", err)
  76. return
  77. }
  78. apiComments := make([]*api.Comment, len(comments))
  79. for i, comment := range comments {
  80. comment.Issue = issue
  81. apiComments[i] = convert.ToComment(comments[i])
  82. }
  83. ctx.JSON(http.StatusOK, &apiComments)
  84. }
  85. // ListRepoIssueComments returns all issue-comments for a repo
  86. func ListRepoIssueComments(ctx *context.APIContext) {
  87. // swagger:operation GET /repos/{owner}/{repo}/issues/comments issue issueGetRepoComments
  88. // ---
  89. // summary: List all comments in a repository
  90. // produces:
  91. // - application/json
  92. // parameters:
  93. // - name: owner
  94. // in: path
  95. // description: owner of the repo
  96. // type: string
  97. // required: true
  98. // - name: repo
  99. // in: path
  100. // description: name of the repo
  101. // type: string
  102. // required: true
  103. // - name: since
  104. // in: query
  105. // description: if provided, only comments updated since the provided time are returned.
  106. // type: string
  107. // format: date-time
  108. // - name: before
  109. // in: query
  110. // description: if provided, only comments updated before the provided time are returned.
  111. // type: string
  112. // format: date-time
  113. // - name: page
  114. // in: query
  115. // description: page number of results to return (1-based)
  116. // type: integer
  117. // - name: limit
  118. // in: query
  119. // description: page size of results
  120. // type: integer
  121. // responses:
  122. // "200":
  123. // "$ref": "#/responses/CommentList"
  124. before, since, err := utils.GetQueryBeforeSince(ctx)
  125. if err != nil {
  126. ctx.Error(http.StatusInternalServerError, "GetQueryBeforeSince", err)
  127. return
  128. }
  129. comments, err := models.FindComments(models.FindCommentsOptions{
  130. ListOptions: utils.GetListOptions(ctx),
  131. RepoID: ctx.Repo.Repository.ID,
  132. Type: models.CommentTypeComment,
  133. Since: since,
  134. Before: before,
  135. })
  136. if err != nil {
  137. ctx.Error(http.StatusInternalServerError, "FindComments", err)
  138. return
  139. }
  140. if err = models.CommentList(comments).LoadPosters(); err != nil {
  141. ctx.Error(http.StatusInternalServerError, "LoadPosters", err)
  142. return
  143. }
  144. apiComments := make([]*api.Comment, len(comments))
  145. if err := models.CommentList(comments).LoadIssues(); err != nil {
  146. ctx.Error(http.StatusInternalServerError, "LoadIssues", err)
  147. return
  148. }
  149. if err := models.CommentList(comments).LoadPosters(); err != nil {
  150. ctx.Error(http.StatusInternalServerError, "LoadPosters", err)
  151. return
  152. }
  153. if _, err := models.CommentList(comments).Issues().LoadRepositories(); err != nil {
  154. ctx.Error(http.StatusInternalServerError, "LoadRepositories", err)
  155. return
  156. }
  157. for i := range comments {
  158. apiComments[i] = convert.ToComment(comments[i])
  159. }
  160. ctx.JSON(http.StatusOK, &apiComments)
  161. }
  162. // CreateIssueComment create a comment for an issue
  163. func CreateIssueComment(ctx *context.APIContext, form api.CreateIssueCommentOption) {
  164. // swagger:operation POST /repos/{owner}/{repo}/issues/{index}/comments issue issueCreateComment
  165. // ---
  166. // summary: Add a comment to an issue
  167. // consumes:
  168. // - application/json
  169. // produces:
  170. // - application/json
  171. // parameters:
  172. // - name: owner
  173. // in: path
  174. // description: owner of the repo
  175. // type: string
  176. // required: true
  177. // - name: repo
  178. // in: path
  179. // description: name of the repo
  180. // type: string
  181. // required: true
  182. // - name: index
  183. // in: path
  184. // description: index of the issue
  185. // type: integer
  186. // format: int64
  187. // required: true
  188. // - name: body
  189. // in: body
  190. // schema:
  191. // "$ref": "#/definitions/CreateIssueCommentOption"
  192. // responses:
  193. // "201":
  194. // "$ref": "#/responses/Comment"
  195. // "403":
  196. // "$ref": "#/responses/forbidden"
  197. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  198. if err != nil {
  199. ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
  200. return
  201. }
  202. if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.User.IsAdmin {
  203. ctx.Error(http.StatusForbidden, "CreateIssueComment", errors.New(ctx.Tr("repo.issues.comment_on_locked")))
  204. return
  205. }
  206. comment, err := comment_service.CreateIssueComment(ctx.User, ctx.Repo.Repository, issue, form.Body, nil)
  207. if err != nil {
  208. ctx.Error(http.StatusInternalServerError, "CreateIssueComment", err)
  209. return
  210. }
  211. ctx.JSON(http.StatusCreated, convert.ToComment(comment))
  212. }
  213. // GetIssueComment Get a comment by ID
  214. func GetIssueComment(ctx *context.APIContext) {
  215. // swagger:operation GET /repos/{owner}/{repo}/issues/comments/{id} issue issueGetComment
  216. // ---
  217. // summary: Get a comment
  218. // consumes:
  219. // - application/json
  220. // produces:
  221. // - application/json
  222. // parameters:
  223. // - name: owner
  224. // in: path
  225. // description: owner of the repo
  226. // type: string
  227. // required: true
  228. // - name: repo
  229. // in: path
  230. // description: name of the repo
  231. // type: string
  232. // required: true
  233. // - name: id
  234. // in: path
  235. // description: id of the comment
  236. // type: integer
  237. // format: int64
  238. // required: true
  239. // responses:
  240. // "200":
  241. // "$ref": "#/responses/Comment"
  242. // "204":
  243. // "$ref": "#/responses/empty"
  244. // "403":
  245. // "$ref": "#/responses/forbidden"
  246. // "404":
  247. // "$ref": "#/responses/notFound"
  248. comment, err := models.GetCommentByID(ctx.ParamsInt64(":id"))
  249. if err != nil {
  250. if models.IsErrCommentNotExist(err) {
  251. ctx.NotFound(err)
  252. } else {
  253. ctx.Error(http.StatusInternalServerError, "GetCommentByID", err)
  254. }
  255. return
  256. }
  257. if err = comment.LoadIssue(); err != nil {
  258. ctx.InternalServerError(err)
  259. return
  260. }
  261. if comment.Issue.RepoID != ctx.Repo.Repository.ID {
  262. ctx.Status(http.StatusNotFound)
  263. return
  264. }
  265. if comment.Type != models.CommentTypeComment {
  266. ctx.Status(http.StatusNoContent)
  267. return
  268. }
  269. if err := comment.LoadPoster(); err != nil {
  270. ctx.Error(http.StatusInternalServerError, "comment.LoadPoster", err)
  271. return
  272. }
  273. ctx.JSON(http.StatusOK, convert.ToComment(comment))
  274. }
  275. // EditIssueComment modify a comment of an issue
  276. func EditIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption) {
  277. // swagger:operation PATCH /repos/{owner}/{repo}/issues/comments/{id} issue issueEditComment
  278. // ---
  279. // summary: Edit a comment
  280. // consumes:
  281. // - application/json
  282. // produces:
  283. // - application/json
  284. // parameters:
  285. // - name: owner
  286. // in: path
  287. // description: owner of the repo
  288. // type: string
  289. // required: true
  290. // - name: repo
  291. // in: path
  292. // description: name of the repo
  293. // type: string
  294. // required: true
  295. // - name: id
  296. // in: path
  297. // description: id of the comment to edit
  298. // type: integer
  299. // format: int64
  300. // required: true
  301. // - name: body
  302. // in: body
  303. // schema:
  304. // "$ref": "#/definitions/EditIssueCommentOption"
  305. // responses:
  306. // "200":
  307. // "$ref": "#/responses/Comment"
  308. // "204":
  309. // "$ref": "#/responses/empty"
  310. // "403":
  311. // "$ref": "#/responses/forbidden"
  312. // "404":
  313. // "$ref": "#/responses/notFound"
  314. editIssueComment(ctx, form)
  315. }
  316. // EditIssueCommentDeprecated modify a comment of an issue
  317. func EditIssueCommentDeprecated(ctx *context.APIContext, form api.EditIssueCommentOption) {
  318. // swagger:operation PATCH /repos/{owner}/{repo}/issues/{index}/comments/{id} issue issueEditCommentDeprecated
  319. // ---
  320. // summary: Edit a comment
  321. // deprecated: true
  322. // consumes:
  323. // - application/json
  324. // produces:
  325. // - application/json
  326. // parameters:
  327. // - name: owner
  328. // in: path
  329. // description: owner of the repo
  330. // type: string
  331. // required: true
  332. // - name: repo
  333. // in: path
  334. // description: name of the repo
  335. // type: string
  336. // required: true
  337. // - name: index
  338. // in: path
  339. // description: this parameter is ignored
  340. // type: integer
  341. // required: true
  342. // - name: id
  343. // in: path
  344. // description: id of the comment to edit
  345. // type: integer
  346. // format: int64
  347. // required: true
  348. // - name: body
  349. // in: body
  350. // schema:
  351. // "$ref": "#/definitions/EditIssueCommentOption"
  352. // responses:
  353. // "200":
  354. // "$ref": "#/responses/Comment"
  355. // "204":
  356. // "$ref": "#/responses/empty"
  357. // "403":
  358. // "$ref": "#/responses/forbidden"
  359. // "404":
  360. // "$ref": "#/responses/notFound"
  361. editIssueComment(ctx, form)
  362. }
  363. func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption) {
  364. comment, err := models.GetCommentByID(ctx.ParamsInt64(":id"))
  365. if err != nil {
  366. if models.IsErrCommentNotExist(err) {
  367. ctx.NotFound(err)
  368. } else {
  369. ctx.Error(http.StatusInternalServerError, "GetCommentByID", err)
  370. }
  371. return
  372. }
  373. if !ctx.IsSigned || (ctx.User.ID != comment.PosterID && !ctx.Repo.IsAdmin()) {
  374. ctx.Status(http.StatusForbidden)
  375. return
  376. } else if comment.Type != models.CommentTypeComment {
  377. ctx.Status(http.StatusNoContent)
  378. return
  379. }
  380. oldContent := comment.Content
  381. comment.Content = form.Body
  382. if err := comment_service.UpdateComment(comment, ctx.User, oldContent); err != nil {
  383. ctx.Error(http.StatusInternalServerError, "UpdateComment", err)
  384. return
  385. }
  386. ctx.JSON(http.StatusOK, convert.ToComment(comment))
  387. }
  388. // DeleteIssueComment delete a comment from an issue
  389. func DeleteIssueComment(ctx *context.APIContext) {
  390. // swagger:operation DELETE /repos/{owner}/{repo}/issues/comments/{id} issue issueDeleteComment
  391. // ---
  392. // summary: Delete a comment
  393. // parameters:
  394. // - name: owner
  395. // in: path
  396. // description: owner of the repo
  397. // type: string
  398. // required: true
  399. // - name: repo
  400. // in: path
  401. // description: name of the repo
  402. // type: string
  403. // required: true
  404. // - name: id
  405. // in: path
  406. // description: id of comment to delete
  407. // type: integer
  408. // format: int64
  409. // required: true
  410. // responses:
  411. // "204":
  412. // "$ref": "#/responses/empty"
  413. // "403":
  414. // "$ref": "#/responses/forbidden"
  415. // "404":
  416. // "$ref": "#/responses/notFound"
  417. deleteIssueComment(ctx)
  418. }
  419. // DeleteIssueCommentDeprecated delete a comment from an issue
  420. func DeleteIssueCommentDeprecated(ctx *context.APIContext) {
  421. // swagger:operation DELETE /repos/{owner}/{repo}/issues/{index}/comments/{id} issue issueDeleteCommentDeprecated
  422. // ---
  423. // summary: Delete a comment
  424. // deprecated: true
  425. // parameters:
  426. // - name: owner
  427. // in: path
  428. // description: owner of the repo
  429. // type: string
  430. // required: true
  431. // - name: repo
  432. // in: path
  433. // description: name of the repo
  434. // type: string
  435. // required: true
  436. // - name: index
  437. // in: path
  438. // description: this parameter is ignored
  439. // type: integer
  440. // required: true
  441. // - name: id
  442. // in: path
  443. // description: id of comment to delete
  444. // type: integer
  445. // format: int64
  446. // required: true
  447. // responses:
  448. // "204":
  449. // "$ref": "#/responses/empty"
  450. // "403":
  451. // "$ref": "#/responses/forbidden"
  452. // "404":
  453. // "$ref": "#/responses/notFound"
  454. deleteIssueComment(ctx)
  455. }
  456. func deleteIssueComment(ctx *context.APIContext) {
  457. comment, err := models.GetCommentByID(ctx.ParamsInt64(":id"))
  458. if err != nil {
  459. if models.IsErrCommentNotExist(err) {
  460. ctx.NotFound(err)
  461. } else {
  462. ctx.Error(http.StatusInternalServerError, "GetCommentByID", err)
  463. }
  464. return
  465. }
  466. if !ctx.IsSigned || (ctx.User.ID != comment.PosterID && !ctx.Repo.IsAdmin()) {
  467. ctx.Status(http.StatusForbidden)
  468. return
  469. } else if comment.Type != models.CommentTypeComment {
  470. ctx.Status(http.StatusNoContent)
  471. return
  472. }
  473. if err = comment_service.DeleteComment(comment, ctx.User); err != nil {
  474. ctx.Error(http.StatusInternalServerError, "DeleteCommentByID", err)
  475. return
  476. }
  477. ctx.Status(http.StatusNoContent)
  478. }