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 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724
  1. // Copyright 2015 The Gogs Authors. All rights reserved.
  2. // Copyright 2020 The Gitea Authors.
  3. // SPDX-License-Identifier: MIT
  4. package repo
  5. import (
  6. stdCtx "context"
  7. "errors"
  8. "net/http"
  9. issues_model "code.gitea.io/gitea/models/issues"
  10. access_model "code.gitea.io/gitea/models/perm/access"
  11. repo_model "code.gitea.io/gitea/models/repo"
  12. "code.gitea.io/gitea/models/unit"
  13. user_model "code.gitea.io/gitea/models/user"
  14. "code.gitea.io/gitea/modules/context"
  15. api "code.gitea.io/gitea/modules/structs"
  16. "code.gitea.io/gitea/modules/util"
  17. "code.gitea.io/gitea/modules/web"
  18. "code.gitea.io/gitea/routers/api/v1/utils"
  19. "code.gitea.io/gitea/services/convert"
  20. issue_service "code.gitea.io/gitea/services/issue"
  21. )
  22. // ListIssueComments list all the comments of an issue
  23. func ListIssueComments(ctx *context.APIContext) {
  24. // swagger:operation GET /repos/{owner}/{repo}/issues/{index}/comments issue issueGetComments
  25. // ---
  26. // summary: List all comments on an issue
  27. // produces:
  28. // - application/json
  29. // parameters:
  30. // - name: owner
  31. // in: path
  32. // description: owner of the repo
  33. // type: string
  34. // required: true
  35. // - name: repo
  36. // in: path
  37. // description: name of the repo
  38. // type: string
  39. // required: true
  40. // - name: index
  41. // in: path
  42. // description: index of the issue
  43. // type: integer
  44. // format: int64
  45. // required: true
  46. // - name: since
  47. // in: query
  48. // description: if provided, only comments updated since the specified time are returned.
  49. // type: string
  50. // format: date-time
  51. // - name: before
  52. // in: query
  53. // description: if provided, only comments updated before the provided time are returned.
  54. // type: string
  55. // format: date-time
  56. // responses:
  57. // "200":
  58. // "$ref": "#/responses/CommentList"
  59. // "404":
  60. // "$ref": "#/responses/notFound"
  61. before, since, err := context.GetQueryBeforeSince(ctx.Base)
  62. if err != nil {
  63. ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
  64. return
  65. }
  66. issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  67. if err != nil {
  68. ctx.Error(http.StatusInternalServerError, "GetRawIssueByIndex", err)
  69. return
  70. }
  71. if !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull) {
  72. ctx.NotFound()
  73. return
  74. }
  75. issue.Repo = ctx.Repo.Repository
  76. opts := &issues_model.FindCommentsOptions{
  77. IssueID: issue.ID,
  78. Since: since,
  79. Before: before,
  80. Type: issues_model.CommentTypeComment,
  81. }
  82. comments, err := issues_model.FindComments(ctx, opts)
  83. if err != nil {
  84. ctx.Error(http.StatusInternalServerError, "FindComments", err)
  85. return
  86. }
  87. totalCount, err := issues_model.CountComments(ctx, opts)
  88. if err != nil {
  89. ctx.InternalServerError(err)
  90. return
  91. }
  92. if err := comments.LoadPosters(ctx); err != nil {
  93. ctx.Error(http.StatusInternalServerError, "LoadPosters", err)
  94. return
  95. }
  96. if err := comments.LoadAttachments(ctx); err != nil {
  97. ctx.Error(http.StatusInternalServerError, "LoadAttachments", err)
  98. return
  99. }
  100. apiComments := make([]*api.Comment, len(comments))
  101. for i, comment := range comments {
  102. comment.Issue = issue
  103. apiComments[i] = convert.ToAPIComment(ctx, ctx.Repo.Repository, comments[i])
  104. }
  105. ctx.SetTotalCountHeader(totalCount)
  106. ctx.JSON(http.StatusOK, &apiComments)
  107. }
  108. // ListIssueCommentsAndTimeline list all the comments and events of an issue
  109. func ListIssueCommentsAndTimeline(ctx *context.APIContext) {
  110. // swagger:operation GET /repos/{owner}/{repo}/issues/{index}/timeline issue issueGetCommentsAndTimeline
  111. // ---
  112. // summary: List all comments and events on an issue
  113. // produces:
  114. // - application/json
  115. // parameters:
  116. // - name: owner
  117. // in: path
  118. // description: owner of the repo
  119. // type: string
  120. // required: true
  121. // - name: repo
  122. // in: path
  123. // description: name of the repo
  124. // type: string
  125. // required: true
  126. // - name: index
  127. // in: path
  128. // description: index of the issue
  129. // type: integer
  130. // format: int64
  131. // required: true
  132. // - name: since
  133. // in: query
  134. // description: if provided, only comments updated since the specified time are returned.
  135. // type: string
  136. // format: date-time
  137. // - name: page
  138. // in: query
  139. // description: page number of results to return (1-based)
  140. // type: integer
  141. // - name: limit
  142. // in: query
  143. // description: page size of results
  144. // type: integer
  145. // - name: before
  146. // in: query
  147. // description: if provided, only comments updated before the provided time are returned.
  148. // type: string
  149. // format: date-time
  150. // responses:
  151. // "200":
  152. // "$ref": "#/responses/TimelineList"
  153. // "404":
  154. // "$ref": "#/responses/notFound"
  155. before, since, err := context.GetQueryBeforeSince(ctx.Base)
  156. if err != nil {
  157. ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
  158. return
  159. }
  160. issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  161. if err != nil {
  162. ctx.Error(http.StatusInternalServerError, "GetRawIssueByIndex", err)
  163. return
  164. }
  165. issue.Repo = ctx.Repo.Repository
  166. opts := &issues_model.FindCommentsOptions{
  167. ListOptions: utils.GetListOptions(ctx),
  168. IssueID: issue.ID,
  169. Since: since,
  170. Before: before,
  171. Type: issues_model.CommentTypeUndefined,
  172. }
  173. comments, err := issues_model.FindComments(ctx, opts)
  174. if err != nil {
  175. ctx.Error(http.StatusInternalServerError, "FindComments", err)
  176. return
  177. }
  178. if err := comments.LoadPosters(ctx); err != nil {
  179. ctx.Error(http.StatusInternalServerError, "LoadPosters", err)
  180. return
  181. }
  182. var apiComments []*api.TimelineComment
  183. for _, comment := range comments {
  184. if comment.Type != issues_model.CommentTypeCode && isXRefCommentAccessible(ctx, ctx.Doer, comment, issue.RepoID) {
  185. comment.Issue = issue
  186. apiComments = append(apiComments, convert.ToTimelineComment(ctx, issue.Repo, comment, ctx.Doer))
  187. }
  188. }
  189. ctx.SetTotalCountHeader(int64(len(apiComments)))
  190. ctx.JSON(http.StatusOK, &apiComments)
  191. }
  192. func isXRefCommentAccessible(ctx stdCtx.Context, user *user_model.User, c *issues_model.Comment, issueRepoID int64) bool {
  193. // Remove comments that the user has no permissions to see
  194. if issues_model.CommentTypeIsRef(c.Type) && c.RefRepoID != issueRepoID && c.RefRepoID != 0 {
  195. var err error
  196. // Set RefRepo for description in template
  197. c.RefRepo, err = repo_model.GetRepositoryByID(ctx, c.RefRepoID)
  198. if err != nil {
  199. return false
  200. }
  201. perm, err := access_model.GetUserRepoPermission(ctx, c.RefRepo, user)
  202. if err != nil {
  203. return false
  204. }
  205. if !perm.CanReadIssuesOrPulls(c.RefIsPull) {
  206. return false
  207. }
  208. }
  209. return true
  210. }
  211. // ListRepoIssueComments returns all issue-comments for a repo
  212. func ListRepoIssueComments(ctx *context.APIContext) {
  213. // swagger:operation GET /repos/{owner}/{repo}/issues/comments issue issueGetRepoComments
  214. // ---
  215. // summary: List all comments in a repository
  216. // produces:
  217. // - application/json
  218. // parameters:
  219. // - name: owner
  220. // in: path
  221. // description: owner of the repo
  222. // type: string
  223. // required: true
  224. // - name: repo
  225. // in: path
  226. // description: name of the repo
  227. // type: string
  228. // required: true
  229. // - name: since
  230. // in: query
  231. // description: if provided, only comments updated since the provided time are returned.
  232. // type: string
  233. // format: date-time
  234. // - name: before
  235. // in: query
  236. // description: if provided, only comments updated before the provided time are returned.
  237. // type: string
  238. // format: date-time
  239. // - name: page
  240. // in: query
  241. // description: page number of results to return (1-based)
  242. // type: integer
  243. // - name: limit
  244. // in: query
  245. // description: page size of results
  246. // type: integer
  247. // responses:
  248. // "200":
  249. // "$ref": "#/responses/CommentList"
  250. // "404":
  251. // "$ref": "#/responses/notFound"
  252. before, since, err := context.GetQueryBeforeSince(ctx.Base)
  253. if err != nil {
  254. ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
  255. return
  256. }
  257. var isPull util.OptionalBool
  258. canReadIssue := ctx.Repo.CanRead(unit.TypeIssues)
  259. canReadPull := ctx.Repo.CanRead(unit.TypePullRequests)
  260. if canReadIssue && canReadPull {
  261. isPull = util.OptionalBoolNone
  262. } else if canReadIssue {
  263. isPull = util.OptionalBoolFalse
  264. } else if canReadPull {
  265. isPull = util.OptionalBoolTrue
  266. } else {
  267. ctx.NotFound()
  268. return
  269. }
  270. opts := &issues_model.FindCommentsOptions{
  271. ListOptions: utils.GetListOptions(ctx),
  272. RepoID: ctx.Repo.Repository.ID,
  273. Type: issues_model.CommentTypeComment,
  274. Since: since,
  275. Before: before,
  276. IsPull: isPull,
  277. }
  278. comments, err := issues_model.FindComments(ctx, opts)
  279. if err != nil {
  280. ctx.Error(http.StatusInternalServerError, "FindComments", err)
  281. return
  282. }
  283. totalCount, err := issues_model.CountComments(ctx, opts)
  284. if err != nil {
  285. ctx.InternalServerError(err)
  286. return
  287. }
  288. if err = comments.LoadPosters(ctx); err != nil {
  289. ctx.Error(http.StatusInternalServerError, "LoadPosters", err)
  290. return
  291. }
  292. apiComments := make([]*api.Comment, len(comments))
  293. if err := comments.LoadIssues(ctx); err != nil {
  294. ctx.Error(http.StatusInternalServerError, "LoadIssues", err)
  295. return
  296. }
  297. if err := comments.LoadPosters(ctx); err != nil {
  298. ctx.Error(http.StatusInternalServerError, "LoadPosters", err)
  299. return
  300. }
  301. if err := comments.LoadAttachments(ctx); err != nil {
  302. ctx.Error(http.StatusInternalServerError, "LoadAttachments", err)
  303. return
  304. }
  305. if _, err := comments.Issues().LoadRepositories(ctx); err != nil {
  306. ctx.Error(http.StatusInternalServerError, "LoadRepositories", err)
  307. return
  308. }
  309. for i := range comments {
  310. apiComments[i] = convert.ToAPIComment(ctx, ctx.Repo.Repository, comments[i])
  311. }
  312. ctx.SetTotalCountHeader(totalCount)
  313. ctx.JSON(http.StatusOK, &apiComments)
  314. }
  315. // CreateIssueComment create a comment for an issue
  316. func CreateIssueComment(ctx *context.APIContext) {
  317. // swagger:operation POST /repos/{owner}/{repo}/issues/{index}/comments issue issueCreateComment
  318. // ---
  319. // summary: Add a comment to 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: body
  342. // in: body
  343. // schema:
  344. // "$ref": "#/definitions/CreateIssueCommentOption"
  345. // responses:
  346. // "201":
  347. // "$ref": "#/responses/Comment"
  348. // "403":
  349. // "$ref": "#/responses/forbidden"
  350. // "404":
  351. // "$ref": "#/responses/notFound"
  352. form := web.GetForm(ctx).(*api.CreateIssueCommentOption)
  353. issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  354. if err != nil {
  355. ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
  356. return
  357. }
  358. if !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull) {
  359. ctx.NotFound()
  360. return
  361. }
  362. if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.Doer.IsAdmin {
  363. ctx.Error(http.StatusForbidden, "CreateIssueComment", errors.New(ctx.Tr("repo.issues.comment_on_locked")))
  364. return
  365. }
  366. comment, err := issue_service.CreateIssueComment(ctx, ctx.Doer, ctx.Repo.Repository, issue, form.Body, nil)
  367. if err != nil {
  368. ctx.Error(http.StatusInternalServerError, "CreateIssueComment", err)
  369. return
  370. }
  371. ctx.JSON(http.StatusCreated, convert.ToAPIComment(ctx, ctx.Repo.Repository, comment))
  372. }
  373. // GetIssueComment Get a comment by ID
  374. func GetIssueComment(ctx *context.APIContext) {
  375. // swagger:operation GET /repos/{owner}/{repo}/issues/comments/{id} issue issueGetComment
  376. // ---
  377. // summary: Get a comment
  378. // consumes:
  379. // - application/json
  380. // produces:
  381. // - application/json
  382. // parameters:
  383. // - name: owner
  384. // in: path
  385. // description: owner of the repo
  386. // type: string
  387. // required: true
  388. // - name: repo
  389. // in: path
  390. // description: name of the repo
  391. // type: string
  392. // required: true
  393. // - name: id
  394. // in: path
  395. // description: id of the comment
  396. // type: integer
  397. // format: int64
  398. // required: true
  399. // responses:
  400. // "200":
  401. // "$ref": "#/responses/Comment"
  402. // "204":
  403. // "$ref": "#/responses/empty"
  404. // "403":
  405. // "$ref": "#/responses/forbidden"
  406. // "404":
  407. // "$ref": "#/responses/notFound"
  408. comment, err := issues_model.GetCommentByID(ctx, ctx.ParamsInt64(":id"))
  409. if err != nil {
  410. if issues_model.IsErrCommentNotExist(err) {
  411. ctx.NotFound(err)
  412. } else {
  413. ctx.Error(http.StatusInternalServerError, "GetCommentByID", err)
  414. }
  415. return
  416. }
  417. if err = comment.LoadIssue(ctx); err != nil {
  418. ctx.InternalServerError(err)
  419. return
  420. }
  421. if comment.Issue.RepoID != ctx.Repo.Repository.ID {
  422. ctx.Status(http.StatusNotFound)
  423. return
  424. }
  425. if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
  426. ctx.NotFound()
  427. return
  428. }
  429. if comment.Type != issues_model.CommentTypeComment {
  430. ctx.Status(http.StatusNoContent)
  431. return
  432. }
  433. if err := comment.LoadPoster(ctx); err != nil {
  434. ctx.Error(http.StatusInternalServerError, "comment.LoadPoster", err)
  435. return
  436. }
  437. ctx.JSON(http.StatusOK, convert.ToAPIComment(ctx, ctx.Repo.Repository, comment))
  438. }
  439. // EditIssueComment modify a comment of an issue
  440. func EditIssueComment(ctx *context.APIContext) {
  441. // swagger:operation PATCH /repos/{owner}/{repo}/issues/comments/{id} issue issueEditComment
  442. // ---
  443. // summary: Edit a comment
  444. // consumes:
  445. // - application/json
  446. // produces:
  447. // - application/json
  448. // parameters:
  449. // - name: owner
  450. // in: path
  451. // description: owner of the repo
  452. // type: string
  453. // required: true
  454. // - name: repo
  455. // in: path
  456. // description: name of the repo
  457. // type: string
  458. // required: true
  459. // - name: id
  460. // in: path
  461. // description: id of the comment to edit
  462. // type: integer
  463. // format: int64
  464. // required: true
  465. // - name: body
  466. // in: body
  467. // schema:
  468. // "$ref": "#/definitions/EditIssueCommentOption"
  469. // responses:
  470. // "200":
  471. // "$ref": "#/responses/Comment"
  472. // "204":
  473. // "$ref": "#/responses/empty"
  474. // "403":
  475. // "$ref": "#/responses/forbidden"
  476. // "404":
  477. // "$ref": "#/responses/notFound"
  478. form := web.GetForm(ctx).(*api.EditIssueCommentOption)
  479. editIssueComment(ctx, *form)
  480. }
  481. // EditIssueCommentDeprecated modify a comment of an issue
  482. func EditIssueCommentDeprecated(ctx *context.APIContext) {
  483. // swagger:operation PATCH /repos/{owner}/{repo}/issues/{index}/comments/{id} issue issueEditCommentDeprecated
  484. // ---
  485. // summary: Edit a comment
  486. // deprecated: true
  487. // consumes:
  488. // - application/json
  489. // produces:
  490. // - application/json
  491. // parameters:
  492. // - name: owner
  493. // in: path
  494. // description: owner of the repo
  495. // type: string
  496. // required: true
  497. // - name: repo
  498. // in: path
  499. // description: name of the repo
  500. // type: string
  501. // required: true
  502. // - name: index
  503. // in: path
  504. // description: this parameter is ignored
  505. // type: integer
  506. // required: true
  507. // - name: id
  508. // in: path
  509. // description: id of the comment to edit
  510. // type: integer
  511. // format: int64
  512. // required: true
  513. // - name: body
  514. // in: body
  515. // schema:
  516. // "$ref": "#/definitions/EditIssueCommentOption"
  517. // responses:
  518. // "200":
  519. // "$ref": "#/responses/Comment"
  520. // "204":
  521. // "$ref": "#/responses/empty"
  522. // "403":
  523. // "$ref": "#/responses/forbidden"
  524. // "404":
  525. // "$ref": "#/responses/notFound"
  526. form := web.GetForm(ctx).(*api.EditIssueCommentOption)
  527. editIssueComment(ctx, *form)
  528. }
  529. func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption) {
  530. comment, err := issues_model.GetCommentByID(ctx, ctx.ParamsInt64(":id"))
  531. if err != nil {
  532. if issues_model.IsErrCommentNotExist(err) {
  533. ctx.NotFound(err)
  534. } else {
  535. ctx.Error(http.StatusInternalServerError, "GetCommentByID", err)
  536. }
  537. return
  538. }
  539. if err := comment.LoadIssue(ctx); err != nil {
  540. ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
  541. return
  542. }
  543. if comment.Issue.RepoID != ctx.Repo.Repository.ID {
  544. ctx.Status(http.StatusNotFound)
  545. return
  546. }
  547. if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
  548. ctx.Status(http.StatusForbidden)
  549. return
  550. }
  551. if !comment.Type.HasContentSupport() {
  552. ctx.Status(http.StatusNoContent)
  553. return
  554. }
  555. oldContent := comment.Content
  556. comment.Content = form.Body
  557. if err := issue_service.UpdateComment(ctx, comment, ctx.Doer, oldContent); err != nil {
  558. ctx.Error(http.StatusInternalServerError, "UpdateComment", err)
  559. return
  560. }
  561. ctx.JSON(http.StatusOK, convert.ToAPIComment(ctx, ctx.Repo.Repository, comment))
  562. }
  563. // DeleteIssueComment delete a comment from an issue
  564. func DeleteIssueComment(ctx *context.APIContext) {
  565. // swagger:operation DELETE /repos/{owner}/{repo}/issues/comments/{id} issue issueDeleteComment
  566. // ---
  567. // summary: Delete a comment
  568. // parameters:
  569. // - name: owner
  570. // in: path
  571. // description: owner of the repo
  572. // type: string
  573. // required: true
  574. // - name: repo
  575. // in: path
  576. // description: name of the repo
  577. // type: string
  578. // required: true
  579. // - name: id
  580. // in: path
  581. // description: id of comment to delete
  582. // type: integer
  583. // format: int64
  584. // required: true
  585. // responses:
  586. // "204":
  587. // "$ref": "#/responses/empty"
  588. // "403":
  589. // "$ref": "#/responses/forbidden"
  590. // "404":
  591. // "$ref": "#/responses/notFound"
  592. deleteIssueComment(ctx)
  593. }
  594. // DeleteIssueCommentDeprecated delete a comment from an issue
  595. func DeleteIssueCommentDeprecated(ctx *context.APIContext) {
  596. // swagger:operation DELETE /repos/{owner}/{repo}/issues/{index}/comments/{id} issue issueDeleteCommentDeprecated
  597. // ---
  598. // summary: Delete a comment
  599. // deprecated: true
  600. // parameters:
  601. // - name: owner
  602. // in: path
  603. // description: owner of the repo
  604. // type: string
  605. // required: true
  606. // - name: repo
  607. // in: path
  608. // description: name of the repo
  609. // type: string
  610. // required: true
  611. // - name: index
  612. // in: path
  613. // description: this parameter is ignored
  614. // type: integer
  615. // required: true
  616. // - name: id
  617. // in: path
  618. // description: id of comment to delete
  619. // type: integer
  620. // format: int64
  621. // required: true
  622. // responses:
  623. // "204":
  624. // "$ref": "#/responses/empty"
  625. // "403":
  626. // "$ref": "#/responses/forbidden"
  627. // "404":
  628. // "$ref": "#/responses/notFound"
  629. deleteIssueComment(ctx)
  630. }
  631. func deleteIssueComment(ctx *context.APIContext) {
  632. comment, err := issues_model.GetCommentByID(ctx, ctx.ParamsInt64(":id"))
  633. if err != nil {
  634. if issues_model.IsErrCommentNotExist(err) {
  635. ctx.NotFound(err)
  636. } else {
  637. ctx.Error(http.StatusInternalServerError, "GetCommentByID", err)
  638. }
  639. return
  640. }
  641. if err := comment.LoadIssue(ctx); err != nil {
  642. ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
  643. return
  644. }
  645. if comment.Issue.RepoID != ctx.Repo.Repository.ID {
  646. ctx.Status(http.StatusNotFound)
  647. return
  648. }
  649. if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
  650. ctx.Status(http.StatusForbidden)
  651. return
  652. } else if comment.Type != issues_model.CommentTypeComment {
  653. ctx.Status(http.StatusNoContent)
  654. return
  655. }
  656. if err = issue_service.DeleteComment(ctx, ctx.Doer, comment); err != nil {
  657. ctx.Error(http.StatusInternalServerError, "DeleteCommentByID", err)
  658. return
  659. }
  660. ctx.Status(http.StatusNoContent)
  661. }