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

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