gitea源码

issue_comment.go 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736
  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/optional"
  15. api "code.gitea.io/gitea/modules/structs"
  16. "code.gitea.io/gitea/modules/web"
  17. "code.gitea.io/gitea/routers/api/v1/utils"
  18. "code.gitea.io/gitea/services/context"
  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.APIError(http.StatusUnprocessableEntity, err)
  64. return
  65. }
  66. issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
  67. if err != nil {
  68. ctx.APIErrorInternal(err)
  69. return
  70. }
  71. if !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull) {
  72. ctx.APIErrorNotFound()
  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.APIErrorInternal(err)
  85. return
  86. }
  87. totalCount, err := issues_model.CountComments(ctx, opts)
  88. if err != nil {
  89. ctx.APIErrorInternal(err)
  90. return
  91. }
  92. if err := comments.LoadPosters(ctx); err != nil {
  93. ctx.APIErrorInternal(err)
  94. return
  95. }
  96. if err := comments.LoadAttachments(ctx); err != nil {
  97. ctx.APIErrorInternal(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.APIError(http.StatusUnprocessableEntity, err)
  158. return
  159. }
  160. issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
  161. if err != nil {
  162. ctx.APIErrorInternal(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.APIErrorInternal(err)
  176. return
  177. }
  178. if err := comments.LoadPosters(ctx); err != nil {
  179. ctx.APIErrorInternal(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.APIError(http.StatusUnprocessableEntity, err)
  255. return
  256. }
  257. var isPull optional.Option[bool]
  258. canReadIssue := ctx.Repo.CanRead(unit.TypeIssues)
  259. canReadPull := ctx.Repo.CanRead(unit.TypePullRequests)
  260. if canReadIssue && canReadPull {
  261. isPull = optional.None[bool]()
  262. } else if canReadIssue {
  263. isPull = optional.Some(false)
  264. } else if canReadPull {
  265. isPull = optional.Some(true)
  266. } else {
  267. ctx.APIErrorNotFound()
  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.APIErrorInternal(err)
  281. return
  282. }
  283. totalCount, err := issues_model.CountComments(ctx, opts)
  284. if err != nil {
  285. ctx.APIErrorInternal(err)
  286. return
  287. }
  288. if err = comments.LoadPosters(ctx); err != nil {
  289. ctx.APIErrorInternal(err)
  290. return
  291. }
  292. apiComments := make([]*api.Comment, len(comments))
  293. if err := comments.LoadIssues(ctx); err != nil {
  294. ctx.APIErrorInternal(err)
  295. return
  296. }
  297. if err := comments.LoadAttachments(ctx); err != nil {
  298. ctx.APIErrorInternal(err)
  299. return
  300. }
  301. if _, err := comments.Issues().LoadRepositories(ctx); err != nil {
  302. ctx.APIErrorInternal(err)
  303. return
  304. }
  305. for i := range comments {
  306. apiComments[i] = convert.ToAPIComment(ctx, ctx.Repo.Repository, comments[i])
  307. }
  308. ctx.SetTotalCountHeader(totalCount)
  309. ctx.JSON(http.StatusOK, &apiComments)
  310. }
  311. // CreateIssueComment create a comment for an issue
  312. func CreateIssueComment(ctx *context.APIContext) {
  313. // swagger:operation POST /repos/{owner}/{repo}/issues/{index}/comments issue issueCreateComment
  314. // ---
  315. // summary: Add a comment to an issue
  316. // consumes:
  317. // - application/json
  318. // produces:
  319. // - application/json
  320. // parameters:
  321. // - name: owner
  322. // in: path
  323. // description: owner of the repo
  324. // type: string
  325. // required: true
  326. // - name: repo
  327. // in: path
  328. // description: name of the repo
  329. // type: string
  330. // required: true
  331. // - name: index
  332. // in: path
  333. // description: index of the issue
  334. // type: integer
  335. // format: int64
  336. // required: true
  337. // - name: body
  338. // in: body
  339. // schema:
  340. // "$ref": "#/definitions/CreateIssueCommentOption"
  341. // responses:
  342. // "201":
  343. // "$ref": "#/responses/Comment"
  344. // "403":
  345. // "$ref": "#/responses/forbidden"
  346. // "404":
  347. // "$ref": "#/responses/notFound"
  348. // "423":
  349. // "$ref": "#/responses/repoArchivedError"
  350. form := web.GetForm(ctx).(*api.CreateIssueCommentOption)
  351. issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
  352. if err != nil {
  353. ctx.APIErrorInternal(err)
  354. return
  355. }
  356. if !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull) {
  357. ctx.APIErrorNotFound()
  358. return
  359. }
  360. if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.Doer.IsAdmin {
  361. ctx.APIError(http.StatusForbidden, errors.New(ctx.Locale.TrString("repo.issues.comment_on_locked")))
  362. return
  363. }
  364. comment, err := issue_service.CreateIssueComment(ctx, ctx.Doer, ctx.Repo.Repository, issue, form.Body, nil)
  365. if err != nil {
  366. if errors.Is(err, user_model.ErrBlockedUser) {
  367. ctx.APIError(http.StatusForbidden, err)
  368. } else {
  369. ctx.APIErrorInternal(err)
  370. }
  371. return
  372. }
  373. ctx.JSON(http.StatusCreated, convert.ToAPIComment(ctx, ctx.Repo.Repository, comment))
  374. }
  375. // GetIssueComment Get a comment by ID
  376. func GetIssueComment(ctx *context.APIContext) {
  377. // swagger:operation GET /repos/{owner}/{repo}/issues/comments/{id} issue issueGetComment
  378. // ---
  379. // summary: Get a comment
  380. // consumes:
  381. // - application/json
  382. // produces:
  383. // - application/json
  384. // parameters:
  385. // - name: owner
  386. // in: path
  387. // description: owner of the repo
  388. // type: string
  389. // required: true
  390. // - name: repo
  391. // in: path
  392. // description: name of the repo
  393. // type: string
  394. // required: true
  395. // - name: id
  396. // in: path
  397. // description: id of the comment
  398. // type: integer
  399. // format: int64
  400. // required: true
  401. // responses:
  402. // "200":
  403. // "$ref": "#/responses/Comment"
  404. // "204":
  405. // "$ref": "#/responses/empty"
  406. // "403":
  407. // "$ref": "#/responses/forbidden"
  408. // "404":
  409. // "$ref": "#/responses/notFound"
  410. comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id"))
  411. if err != nil {
  412. if issues_model.IsErrCommentNotExist(err) {
  413. ctx.APIErrorNotFound(err)
  414. } else {
  415. ctx.APIErrorInternal(err)
  416. }
  417. return
  418. }
  419. if err = comment.LoadIssue(ctx); err != nil {
  420. ctx.APIErrorInternal(err)
  421. return
  422. }
  423. if comment.Issue.RepoID != ctx.Repo.Repository.ID {
  424. ctx.Status(http.StatusNotFound)
  425. return
  426. }
  427. if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
  428. ctx.APIErrorNotFound()
  429. return
  430. }
  431. if comment.Type != issues_model.CommentTypeComment {
  432. ctx.Status(http.StatusNoContent)
  433. return
  434. }
  435. if err := comment.LoadPoster(ctx); err != nil {
  436. ctx.APIErrorInternal(err)
  437. return
  438. }
  439. ctx.JSON(http.StatusOK, convert.ToAPIComment(ctx, ctx.Repo.Repository, comment))
  440. }
  441. // EditIssueComment modify a comment of an issue
  442. func EditIssueComment(ctx *context.APIContext) {
  443. // swagger:operation PATCH /repos/{owner}/{repo}/issues/comments/{id} issue issueEditComment
  444. // ---
  445. // summary: Edit a comment
  446. // consumes:
  447. // - application/json
  448. // produces:
  449. // - application/json
  450. // parameters:
  451. // - name: owner
  452. // in: path
  453. // description: owner of the repo
  454. // type: string
  455. // required: true
  456. // - name: repo
  457. // in: path
  458. // description: name of the repo
  459. // type: string
  460. // required: true
  461. // - name: id
  462. // in: path
  463. // description: id of the comment to edit
  464. // type: integer
  465. // format: int64
  466. // required: true
  467. // - name: body
  468. // in: body
  469. // schema:
  470. // "$ref": "#/definitions/EditIssueCommentOption"
  471. // responses:
  472. // "200":
  473. // "$ref": "#/responses/Comment"
  474. // "204":
  475. // "$ref": "#/responses/empty"
  476. // "403":
  477. // "$ref": "#/responses/forbidden"
  478. // "404":
  479. // "$ref": "#/responses/notFound"
  480. // "423":
  481. // "$ref": "#/responses/repoArchivedError"
  482. form := web.GetForm(ctx).(*api.EditIssueCommentOption)
  483. editIssueComment(ctx, *form)
  484. }
  485. // EditIssueCommentDeprecated modify a comment of an issue
  486. func EditIssueCommentDeprecated(ctx *context.APIContext) {
  487. // swagger:operation PATCH /repos/{owner}/{repo}/issues/{index}/comments/{id} issue issueEditCommentDeprecated
  488. // ---
  489. // summary: Edit a comment
  490. // deprecated: true
  491. // consumes:
  492. // - application/json
  493. // produces:
  494. // - application/json
  495. // parameters:
  496. // - name: owner
  497. // in: path
  498. // description: owner of the repo
  499. // type: string
  500. // required: true
  501. // - name: repo
  502. // in: path
  503. // description: name of the repo
  504. // type: string
  505. // required: true
  506. // - name: index
  507. // in: path
  508. // description: this parameter is ignored
  509. // type: integer
  510. // required: true
  511. // - name: id
  512. // in: path
  513. // description: id of the comment to edit
  514. // type: integer
  515. // format: int64
  516. // required: true
  517. // - name: body
  518. // in: body
  519. // schema:
  520. // "$ref": "#/definitions/EditIssueCommentOption"
  521. // responses:
  522. // "200":
  523. // "$ref": "#/responses/Comment"
  524. // "204":
  525. // "$ref": "#/responses/empty"
  526. // "403":
  527. // "$ref": "#/responses/forbidden"
  528. // "404":
  529. // "$ref": "#/responses/notFound"
  530. form := web.GetForm(ctx).(*api.EditIssueCommentOption)
  531. editIssueComment(ctx, *form)
  532. }
  533. func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption) {
  534. comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id"))
  535. if err != nil {
  536. if issues_model.IsErrCommentNotExist(err) {
  537. ctx.APIErrorNotFound(err)
  538. } else {
  539. ctx.APIErrorInternal(err)
  540. }
  541. return
  542. }
  543. if err := comment.LoadIssue(ctx); err != nil {
  544. ctx.APIErrorInternal(err)
  545. return
  546. }
  547. if comment.Issue.RepoID != ctx.Repo.Repository.ID {
  548. ctx.Status(http.StatusNotFound)
  549. return
  550. }
  551. if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
  552. ctx.Status(http.StatusForbidden)
  553. return
  554. }
  555. if !comment.Type.HasContentSupport() {
  556. ctx.Status(http.StatusNoContent)
  557. return
  558. }
  559. if form.Body != comment.Content {
  560. oldContent := comment.Content
  561. comment.Content = form.Body
  562. if err := issue_service.UpdateComment(ctx, comment, comment.ContentVersion, ctx.Doer, oldContent); err != nil {
  563. if errors.Is(err, user_model.ErrBlockedUser) {
  564. ctx.APIError(http.StatusForbidden, err)
  565. } else {
  566. ctx.APIErrorInternal(err)
  567. }
  568. return
  569. }
  570. }
  571. ctx.JSON(http.StatusOK, convert.ToAPIComment(ctx, ctx.Repo.Repository, comment))
  572. }
  573. // DeleteIssueComment delete a comment from an issue
  574. func DeleteIssueComment(ctx *context.APIContext) {
  575. // swagger:operation DELETE /repos/{owner}/{repo}/issues/comments/{id} issue issueDeleteComment
  576. // ---
  577. // summary: Delete a comment
  578. // parameters:
  579. // - name: owner
  580. // in: path
  581. // description: owner of the repo
  582. // type: string
  583. // required: true
  584. // - name: repo
  585. // in: path
  586. // description: name of the repo
  587. // type: string
  588. // required: true
  589. // - name: id
  590. // in: path
  591. // description: id of comment to delete
  592. // type: integer
  593. // format: int64
  594. // required: true
  595. // responses:
  596. // "204":
  597. // "$ref": "#/responses/empty"
  598. // "403":
  599. // "$ref": "#/responses/forbidden"
  600. // "404":
  601. // "$ref": "#/responses/notFound"
  602. deleteIssueComment(ctx)
  603. }
  604. // DeleteIssueCommentDeprecated delete a comment from an issue
  605. func DeleteIssueCommentDeprecated(ctx *context.APIContext) {
  606. // swagger:operation DELETE /repos/{owner}/{repo}/issues/{index}/comments/{id} issue issueDeleteCommentDeprecated
  607. // ---
  608. // summary: Delete a comment
  609. // deprecated: true
  610. // parameters:
  611. // - name: owner
  612. // in: path
  613. // description: owner of the repo
  614. // type: string
  615. // required: true
  616. // - name: repo
  617. // in: path
  618. // description: name of the repo
  619. // type: string
  620. // required: true
  621. // - name: index
  622. // in: path
  623. // description: this parameter is ignored
  624. // type: integer
  625. // required: true
  626. // - name: id
  627. // in: path
  628. // description: id of comment to delete
  629. // type: integer
  630. // format: int64
  631. // required: true
  632. // responses:
  633. // "204":
  634. // "$ref": "#/responses/empty"
  635. // "403":
  636. // "$ref": "#/responses/forbidden"
  637. // "404":
  638. // "$ref": "#/responses/notFound"
  639. deleteIssueComment(ctx)
  640. }
  641. func deleteIssueComment(ctx *context.APIContext) {
  642. comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id"))
  643. if err != nil {
  644. if issues_model.IsErrCommentNotExist(err) {
  645. ctx.APIErrorNotFound(err)
  646. } else {
  647. ctx.APIErrorInternal(err)
  648. }
  649. return
  650. }
  651. if err := comment.LoadIssue(ctx); err != nil {
  652. ctx.APIErrorInternal(err)
  653. return
  654. }
  655. if comment.Issue.RepoID != ctx.Repo.Repository.ID {
  656. ctx.Status(http.StatusNotFound)
  657. return
  658. }
  659. if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
  660. ctx.Status(http.StatusForbidden)
  661. return
  662. } else if !comment.Type.HasContentSupport() {
  663. ctx.Status(http.StatusBadRequest)
  664. return
  665. }
  666. if err = issue_service.DeleteComment(ctx, ctx.Doer, comment); err != nil {
  667. ctx.APIErrorInternal(err)
  668. return
  669. }
  670. ctx.Status(http.StatusNoContent)
  671. }