Fork to maintain patches against the official gitea for https://code.ceondo.com https://github.com/go-gitea/gitea

issue_comment.go 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  1. // Copyright 2016 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package models
  5. import (
  6. "fmt"
  7. "strings"
  8. "time"
  9. "github.com/Unknwon/com"
  10. "github.com/go-xorm/builder"
  11. "github.com/go-xorm/xorm"
  12. api "code.gitea.io/sdk/gitea"
  13. "code.gitea.io/gitea/modules/log"
  14. "code.gitea.io/gitea/modules/markup"
  15. )
  16. // CommentType defines whether a comment is just a simple comment, an action (like close) or a reference.
  17. type CommentType int
  18. // define unknown comment type
  19. const (
  20. CommentTypeUnknown CommentType = -1
  21. )
  22. // Enumerate all the comment types
  23. const (
  24. // Plain comment, can be associated with a commit (CommitID > 0) and a line (LineNum > 0)
  25. CommentTypeComment CommentType = iota
  26. CommentTypeReopen
  27. CommentTypeClose
  28. // References.
  29. CommentTypeIssueRef
  30. // Reference from a commit (not part of a pull request)
  31. CommentTypeCommitRef
  32. // Reference from a comment
  33. CommentTypeCommentRef
  34. // Reference from a pull request
  35. CommentTypePullRef
  36. // Labels changed
  37. CommentTypeLabel
  38. // Milestone changed
  39. CommentTypeMilestone
  40. // Assignees changed
  41. CommentTypeAssignees
  42. // Change Title
  43. CommentTypeChangeTitle
  44. // Delete Branch
  45. CommentTypeDeleteBranch
  46. // Start a stopwatch for time tracking
  47. CommentTypeStartTracking
  48. // Stop a stopwatch for time tracking
  49. CommentTypeStopTracking
  50. // Add time manual for time tracking
  51. CommentTypeAddTimeManual
  52. // Cancel a stopwatch for time tracking
  53. CommentTypeCancelTracking
  54. )
  55. // CommentTag defines comment tag type
  56. type CommentTag int
  57. // Enumerate all the comment tag types
  58. const (
  59. CommentTagNone CommentTag = iota
  60. CommentTagPoster
  61. CommentTagWriter
  62. CommentTagOwner
  63. )
  64. // Comment represents a comment in commit and issue page.
  65. type Comment struct {
  66. ID int64 `xorm:"pk autoincr"`
  67. Type CommentType
  68. PosterID int64 `xorm:"INDEX"`
  69. Poster *User `xorm:"-"`
  70. IssueID int64 `xorm:"INDEX"`
  71. LabelID int64
  72. Label *Label `xorm:"-"`
  73. OldMilestoneID int64
  74. MilestoneID int64
  75. OldMilestone *Milestone `xorm:"-"`
  76. Milestone *Milestone `xorm:"-"`
  77. OldAssigneeID int64
  78. AssigneeID int64
  79. Assignee *User `xorm:"-"`
  80. OldAssignee *User `xorm:"-"`
  81. OldTitle string
  82. NewTitle string
  83. CommitID int64
  84. Line int64
  85. Content string `xorm:"TEXT"`
  86. RenderedContent string `xorm:"-"`
  87. Created time.Time `xorm:"-"`
  88. CreatedUnix int64 `xorm:"INDEX created"`
  89. Updated time.Time `xorm:"-"`
  90. UpdatedUnix int64 `xorm:"INDEX updated"`
  91. // Reference issue in commit message
  92. CommitSHA string `xorm:"VARCHAR(40)"`
  93. Attachments []*Attachment `xorm:"-"`
  94. Reactions ReactionList `xorm:"-"`
  95. // For view issue page.
  96. ShowTag CommentTag `xorm:"-"`
  97. }
  98. // AfterLoad is invoked from XORM after setting the values of all fields of this object.
  99. func (c *Comment) AfterLoad(session *xorm.Session) {
  100. c.Created = time.Unix(c.CreatedUnix, 0).Local()
  101. c.Updated = time.Unix(c.UpdatedUnix, 0).Local()
  102. var err error
  103. c.Attachments, err = getAttachmentsByCommentID(session, c.ID)
  104. if err != nil {
  105. log.Error(3, "getAttachmentsByCommentID[%d]: %v", c.ID, err)
  106. }
  107. c.Poster, err = getUserByID(session, c.PosterID)
  108. if err != nil {
  109. if IsErrUserNotExist(err) {
  110. c.PosterID = -1
  111. c.Poster = NewGhostUser()
  112. } else {
  113. log.Error(3, "getUserByID[%d]: %v", c.ID, err)
  114. }
  115. }
  116. }
  117. // AfterDelete is invoked from XORM after the object is deleted.
  118. func (c *Comment) AfterDelete() {
  119. _, err := DeleteAttachmentsByComment(c.ID, true)
  120. if err != nil {
  121. log.Info("Could not delete files for comment %d on issue #%d: %s", c.ID, c.IssueID, err)
  122. }
  123. }
  124. // HTMLURL formats a URL-string to the issue-comment
  125. func (c *Comment) HTMLURL() string {
  126. issue, err := GetIssueByID(c.IssueID)
  127. if err != nil { // Silently dropping errors :unamused:
  128. log.Error(4, "GetIssueByID(%d): %v", c.IssueID, err)
  129. return ""
  130. }
  131. return fmt.Sprintf("%s#%s", issue.HTMLURL(), c.HashTag())
  132. }
  133. // IssueURL formats a URL-string to the issue
  134. func (c *Comment) IssueURL() string {
  135. issue, err := GetIssueByID(c.IssueID)
  136. if err != nil { // Silently dropping errors :unamused:
  137. log.Error(4, "GetIssueByID(%d): %v", c.IssueID, err)
  138. return ""
  139. }
  140. if issue.IsPull {
  141. return ""
  142. }
  143. return issue.HTMLURL()
  144. }
  145. // PRURL formats a URL-string to the pull-request
  146. func (c *Comment) PRURL() string {
  147. issue, err := GetIssueByID(c.IssueID)
  148. if err != nil { // Silently dropping errors :unamused:
  149. log.Error(4, "GetIssueByID(%d): %v", c.IssueID, err)
  150. return ""
  151. }
  152. if !issue.IsPull {
  153. return ""
  154. }
  155. return issue.HTMLURL()
  156. }
  157. // APIFormat converts a Comment to the api.Comment format
  158. func (c *Comment) APIFormat() *api.Comment {
  159. return &api.Comment{
  160. ID: c.ID,
  161. Poster: c.Poster.APIFormat(),
  162. HTMLURL: c.HTMLURL(),
  163. IssueURL: c.IssueURL(),
  164. PRURL: c.PRURL(),
  165. Body: c.Content,
  166. Created: c.Created,
  167. Updated: c.Updated,
  168. }
  169. }
  170. // HashTag returns unique hash tag for comment.
  171. func (c *Comment) HashTag() string {
  172. return "issuecomment-" + com.ToStr(c.ID)
  173. }
  174. // EventTag returns unique event hash tag for comment.
  175. func (c *Comment) EventTag() string {
  176. return "event-" + com.ToStr(c.ID)
  177. }
  178. // LoadLabel if comment.Type is CommentTypeLabel, then load Label
  179. func (c *Comment) LoadLabel() error {
  180. var label Label
  181. has, err := x.ID(c.LabelID).Get(&label)
  182. if err != nil {
  183. return err
  184. } else if has {
  185. c.Label = &label
  186. } else {
  187. // Ignore Label is deleted, but not clear this table
  188. log.Warn("Commit %d cannot load label %d", c.ID, c.LabelID)
  189. }
  190. return nil
  191. }
  192. // LoadMilestone if comment.Type is CommentTypeMilestone, then load milestone
  193. func (c *Comment) LoadMilestone() error {
  194. if c.OldMilestoneID > 0 {
  195. var oldMilestone Milestone
  196. has, err := x.ID(c.OldMilestoneID).Get(&oldMilestone)
  197. if err != nil {
  198. return err
  199. } else if has {
  200. c.OldMilestone = &oldMilestone
  201. }
  202. }
  203. if c.MilestoneID > 0 {
  204. var milestone Milestone
  205. has, err := x.ID(c.MilestoneID).Get(&milestone)
  206. if err != nil {
  207. return err
  208. } else if has {
  209. c.Milestone = &milestone
  210. }
  211. }
  212. return nil
  213. }
  214. // LoadAssignees if comment.Type is CommentTypeAssignees, then load assignees
  215. func (c *Comment) LoadAssignees() error {
  216. var err error
  217. if c.OldAssigneeID > 0 {
  218. c.OldAssignee, err = getUserByID(x, c.OldAssigneeID)
  219. if err != nil {
  220. return err
  221. }
  222. }
  223. if c.AssigneeID > 0 {
  224. c.Assignee, err = getUserByID(x, c.AssigneeID)
  225. if err != nil {
  226. return err
  227. }
  228. }
  229. return nil
  230. }
  231. // MailParticipants sends new comment emails to repository watchers
  232. // and mentioned people.
  233. func (c *Comment) MailParticipants(e Engine, opType ActionType, issue *Issue) (err error) {
  234. mentions := markup.FindAllMentions(c.Content)
  235. if err = UpdateIssueMentions(e, c.IssueID, mentions); err != nil {
  236. return fmt.Errorf("UpdateIssueMentions [%d]: %v", c.IssueID, err)
  237. }
  238. content := c.Content
  239. switch opType {
  240. case ActionCloseIssue:
  241. content = fmt.Sprintf("Closed #%d", issue.Index)
  242. case ActionReopenIssue:
  243. content = fmt.Sprintf("Reopened #%d", issue.Index)
  244. }
  245. if err = mailIssueCommentToParticipants(e, issue, c.Poster, content, c, mentions); err != nil {
  246. log.Error(4, "mailIssueCommentToParticipants: %v", err)
  247. }
  248. return nil
  249. }
  250. func (c *Comment) loadReactions(e Engine) (err error) {
  251. if c.Reactions != nil {
  252. return nil
  253. }
  254. c.Reactions, err = findReactions(e, FindReactionsOptions{
  255. IssueID: c.IssueID,
  256. CommentID: c.ID,
  257. })
  258. if err != nil {
  259. return err
  260. }
  261. // Load reaction user data
  262. if _, err := c.Reactions.LoadUsers(); err != nil {
  263. return err
  264. }
  265. return nil
  266. }
  267. // LoadReactions loads comment reactions
  268. func (c *Comment) LoadReactions() error {
  269. return c.loadReactions(x)
  270. }
  271. func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err error) {
  272. var LabelID int64
  273. if opts.Label != nil {
  274. LabelID = opts.Label.ID
  275. }
  276. comment := &Comment{
  277. Type: opts.Type,
  278. PosterID: opts.Doer.ID,
  279. Poster: opts.Doer,
  280. IssueID: opts.Issue.ID,
  281. LabelID: LabelID,
  282. OldMilestoneID: opts.OldMilestoneID,
  283. MilestoneID: opts.MilestoneID,
  284. OldAssigneeID: opts.OldAssigneeID,
  285. AssigneeID: opts.AssigneeID,
  286. CommitID: opts.CommitID,
  287. CommitSHA: opts.CommitSHA,
  288. Line: opts.LineNum,
  289. Content: opts.Content,
  290. OldTitle: opts.OldTitle,
  291. NewTitle: opts.NewTitle,
  292. }
  293. if _, err = e.Insert(comment); err != nil {
  294. return nil, err
  295. }
  296. if err = opts.Repo.getOwner(e); err != nil {
  297. return nil, err
  298. }
  299. // Compose comment action, could be plain comment, close or reopen issue/pull request.
  300. // This object will be used to notify watchers in the end of function.
  301. act := &Action{
  302. ActUserID: opts.Doer.ID,
  303. ActUser: opts.Doer,
  304. Content: fmt.Sprintf("%d|%s", opts.Issue.Index, strings.Split(opts.Content, "\n")[0]),
  305. RepoID: opts.Repo.ID,
  306. Repo: opts.Repo,
  307. Comment: comment,
  308. CommentID: comment.ID,
  309. IsPrivate: opts.Repo.IsPrivate,
  310. }
  311. // Check comment type.
  312. switch opts.Type {
  313. case CommentTypeComment:
  314. act.OpType = ActionCommentIssue
  315. if _, err = e.Exec("UPDATE `issue` SET num_comments=num_comments+1 WHERE id=?", opts.Issue.ID); err != nil {
  316. return nil, err
  317. }
  318. // Check attachments
  319. attachments := make([]*Attachment, 0, len(opts.Attachments))
  320. for _, uuid := range opts.Attachments {
  321. attach, err := getAttachmentByUUID(e, uuid)
  322. if err != nil {
  323. if IsErrAttachmentNotExist(err) {
  324. continue
  325. }
  326. return nil, fmt.Errorf("getAttachmentByUUID [%s]: %v", uuid, err)
  327. }
  328. attachments = append(attachments, attach)
  329. }
  330. for i := range attachments {
  331. attachments[i].IssueID = opts.Issue.ID
  332. attachments[i].CommentID = comment.ID
  333. // No assign value could be 0, so ignore AllCols().
  334. if _, err = e.ID(attachments[i].ID).Update(attachments[i]); err != nil {
  335. return nil, fmt.Errorf("update attachment [%d]: %v", attachments[i].ID, err)
  336. }
  337. }
  338. case CommentTypeReopen:
  339. act.OpType = ActionReopenIssue
  340. if opts.Issue.IsPull {
  341. act.OpType = ActionReopenPullRequest
  342. }
  343. if opts.Issue.IsPull {
  344. _, err = e.Exec("UPDATE `repository` SET num_closed_pulls=num_closed_pulls-1 WHERE id=?", opts.Repo.ID)
  345. } else {
  346. _, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues-1 WHERE id=?", opts.Repo.ID)
  347. }
  348. if err != nil {
  349. return nil, err
  350. }
  351. case CommentTypeClose:
  352. act.OpType = ActionCloseIssue
  353. if opts.Issue.IsPull {
  354. act.OpType = ActionClosePullRequest
  355. }
  356. if opts.Issue.IsPull {
  357. _, err = e.Exec("UPDATE `repository` SET num_closed_pulls=num_closed_pulls+1 WHERE id=?", opts.Repo.ID)
  358. } else {
  359. _, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues+1 WHERE id=?", opts.Repo.ID)
  360. }
  361. if err != nil {
  362. return nil, err
  363. }
  364. }
  365. // update the issue's updated_unix column
  366. if err = updateIssueCols(e, opts.Issue); err != nil {
  367. return nil, err
  368. }
  369. // Notify watchers for whatever action comes in, ignore if no action type.
  370. if act.OpType > 0 {
  371. if err = notifyWatchers(e, act); err != nil {
  372. log.Error(4, "notifyWatchers: %v", err)
  373. }
  374. if err = comment.MailParticipants(e, act.OpType, opts.Issue); err != nil {
  375. log.Error(4, "MailParticipants: %v", err)
  376. }
  377. }
  378. return comment, nil
  379. }
  380. func createStatusComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue) (*Comment, error) {
  381. cmtType := CommentTypeClose
  382. if !issue.IsClosed {
  383. cmtType = CommentTypeReopen
  384. }
  385. return createComment(e, &CreateCommentOptions{
  386. Type: cmtType,
  387. Doer: doer,
  388. Repo: repo,
  389. Issue: issue,
  390. })
  391. }
  392. func createLabelComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue, label *Label, add bool) (*Comment, error) {
  393. var content string
  394. if add {
  395. content = "1"
  396. }
  397. return createComment(e, &CreateCommentOptions{
  398. Type: CommentTypeLabel,
  399. Doer: doer,
  400. Repo: repo,
  401. Issue: issue,
  402. Label: label,
  403. Content: content,
  404. })
  405. }
  406. func createMilestoneComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue, oldMilestoneID, milestoneID int64) (*Comment, error) {
  407. return createComment(e, &CreateCommentOptions{
  408. Type: CommentTypeMilestone,
  409. Doer: doer,
  410. Repo: repo,
  411. Issue: issue,
  412. OldMilestoneID: oldMilestoneID,
  413. MilestoneID: milestoneID,
  414. })
  415. }
  416. func createAssigneeComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue, oldAssigneeID, assigneeID int64) (*Comment, error) {
  417. return createComment(e, &CreateCommentOptions{
  418. Type: CommentTypeAssignees,
  419. Doer: doer,
  420. Repo: repo,
  421. Issue: issue,
  422. OldAssigneeID: oldAssigneeID,
  423. AssigneeID: assigneeID,
  424. })
  425. }
  426. func createChangeTitleComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue, oldTitle, newTitle string) (*Comment, error) {
  427. return createComment(e, &CreateCommentOptions{
  428. Type: CommentTypeChangeTitle,
  429. Doer: doer,
  430. Repo: repo,
  431. Issue: issue,
  432. OldTitle: oldTitle,
  433. NewTitle: newTitle,
  434. })
  435. }
  436. func createDeleteBranchComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue, branchName string) (*Comment, error) {
  437. return createComment(e, &CreateCommentOptions{
  438. Type: CommentTypeDeleteBranch,
  439. Doer: doer,
  440. Repo: repo,
  441. Issue: issue,
  442. CommitSHA: branchName,
  443. })
  444. }
  445. // CreateCommentOptions defines options for creating comment
  446. type CreateCommentOptions struct {
  447. Type CommentType
  448. Doer *User
  449. Repo *Repository
  450. Issue *Issue
  451. Label *Label
  452. OldMilestoneID int64
  453. MilestoneID int64
  454. OldAssigneeID int64
  455. AssigneeID int64
  456. OldTitle string
  457. NewTitle string
  458. CommitID int64
  459. CommitSHA string
  460. LineNum int64
  461. Content string
  462. Attachments []string // UUIDs of attachments
  463. }
  464. // CreateComment creates comment of issue or commit.
  465. func CreateComment(opts *CreateCommentOptions) (comment *Comment, err error) {
  466. sess := x.NewSession()
  467. defer sess.Close()
  468. if err = sess.Begin(); err != nil {
  469. return nil, err
  470. }
  471. comment, err = createComment(sess, opts)
  472. if err != nil {
  473. return nil, err
  474. }
  475. if err = sess.Commit(); err != nil {
  476. return nil, err
  477. }
  478. if opts.Type == CommentTypeComment {
  479. UpdateIssueIndexer(opts.Issue.ID)
  480. }
  481. return comment, nil
  482. }
  483. // CreateIssueComment creates a plain issue comment.
  484. func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content string, attachments []string) (*Comment, error) {
  485. return CreateComment(&CreateCommentOptions{
  486. Type: CommentTypeComment,
  487. Doer: doer,
  488. Repo: repo,
  489. Issue: issue,
  490. Content: content,
  491. Attachments: attachments,
  492. })
  493. }
  494. // CreateRefComment creates a commit reference comment to issue.
  495. func CreateRefComment(doer *User, repo *Repository, issue *Issue, content, commitSHA string) error {
  496. if len(commitSHA) == 0 {
  497. return fmt.Errorf("cannot create reference with empty commit SHA")
  498. }
  499. // Check if same reference from same commit has already existed.
  500. has, err := x.Get(&Comment{
  501. Type: CommentTypeCommitRef,
  502. IssueID: issue.ID,
  503. CommitSHA: commitSHA,
  504. })
  505. if err != nil {
  506. return fmt.Errorf("check reference comment: %v", err)
  507. } else if has {
  508. return nil
  509. }
  510. _, err = CreateComment(&CreateCommentOptions{
  511. Type: CommentTypeCommitRef,
  512. Doer: doer,
  513. Repo: repo,
  514. Issue: issue,
  515. CommitSHA: commitSHA,
  516. Content: content,
  517. })
  518. return err
  519. }
  520. // GetCommentByID returns the comment by given ID.
  521. func GetCommentByID(id int64) (*Comment, error) {
  522. c := new(Comment)
  523. has, err := x.ID(id).Get(c)
  524. if err != nil {
  525. return nil, err
  526. } else if !has {
  527. return nil, ErrCommentNotExist{id, 0}
  528. }
  529. return c, nil
  530. }
  531. // FindCommentsOptions describes the conditions to Find comments
  532. type FindCommentsOptions struct {
  533. RepoID int64
  534. IssueID int64
  535. Since int64
  536. Type CommentType
  537. }
  538. func (opts *FindCommentsOptions) toConds() builder.Cond {
  539. var cond = builder.NewCond()
  540. if opts.RepoID > 0 {
  541. cond = cond.And(builder.Eq{"issue.repo_id": opts.RepoID})
  542. }
  543. if opts.IssueID > 0 {
  544. cond = cond.And(builder.Eq{"comment.issue_id": opts.IssueID})
  545. }
  546. if opts.Since > 0 {
  547. cond = cond.And(builder.Gte{"comment.updated_unix": opts.Since})
  548. }
  549. if opts.Type != CommentTypeUnknown {
  550. cond = cond.And(builder.Eq{"comment.type": opts.Type})
  551. }
  552. return cond
  553. }
  554. func findComments(e Engine, opts FindCommentsOptions) ([]*Comment, error) {
  555. comments := make([]*Comment, 0, 10)
  556. sess := e.Where(opts.toConds())
  557. if opts.RepoID > 0 {
  558. sess.Join("INNER", "issue", "issue.id = comment.issue_id")
  559. }
  560. return comments, sess.
  561. Asc("comment.created_unix").
  562. Asc("comment.id").
  563. Find(&comments)
  564. }
  565. // FindComments returns all comments according options
  566. func FindComments(opts FindCommentsOptions) ([]*Comment, error) {
  567. return findComments(x, opts)
  568. }
  569. // GetCommentsByIssueID returns all comments of an issue.
  570. func GetCommentsByIssueID(issueID int64) ([]*Comment, error) {
  571. return findComments(x, FindCommentsOptions{
  572. IssueID: issueID,
  573. Type: CommentTypeUnknown,
  574. })
  575. }
  576. // GetCommentsByIssueIDSince returns a list of comments of an issue since a given time point.
  577. func GetCommentsByIssueIDSince(issueID, since int64) ([]*Comment, error) {
  578. return findComments(x, FindCommentsOptions{
  579. IssueID: issueID,
  580. Type: CommentTypeUnknown,
  581. Since: since,
  582. })
  583. }
  584. // GetCommentsByRepoIDSince returns a list of comments for all issues in a repo since a given time point.
  585. func GetCommentsByRepoIDSince(repoID, since int64) ([]*Comment, error) {
  586. return findComments(x, FindCommentsOptions{
  587. RepoID: repoID,
  588. Type: CommentTypeUnknown,
  589. Since: since,
  590. })
  591. }
  592. // UpdateComment updates information of comment.
  593. func UpdateComment(c *Comment) error {
  594. if _, err := x.ID(c.ID).AllCols().Update(c); err != nil {
  595. return err
  596. } else if c.Type == CommentTypeComment {
  597. UpdateIssueIndexer(c.IssueID)
  598. }
  599. return nil
  600. }
  601. // DeleteComment deletes the comment
  602. func DeleteComment(comment *Comment) error {
  603. sess := x.NewSession()
  604. defer sess.Close()
  605. if err := sess.Begin(); err != nil {
  606. return err
  607. }
  608. if _, err := sess.Delete(&Comment{
  609. ID: comment.ID,
  610. }); err != nil {
  611. return err
  612. }
  613. if comment.Type == CommentTypeComment {
  614. if _, err := sess.Exec("UPDATE `issue` SET num_comments = num_comments - 1 WHERE id = ?", comment.IssueID); err != nil {
  615. return err
  616. }
  617. }
  618. if _, err := sess.Where("comment_id = ?", comment.ID).Cols("is_deleted").Update(&Action{IsDeleted: true}); err != nil {
  619. return err
  620. }
  621. if err := sess.Commit(); err != nil {
  622. return err
  623. } else if comment.Type == CommentTypeComment {
  624. UpdateIssueIndexer(comment.IssueID)
  625. }
  626. return nil
  627. }