go-gitea/models/issue_comment.go
Keith Rutkowski f25b15358c Add the LoadReference function
Add the LoadReference function to prepare data members of reference
types of comments for frontend rendering.  Also prepares for the
transition of commit reference comments into a new message format.
2018-07-06 08:04:33 -04:00

961 lines
25 KiB
Go

// Copyright 2016 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
"fmt"
"strings"
"github.com/Unknwon/com"
"github.com/go-xorm/builder"
"github.com/go-xorm/xorm"
api "code.gitea.io/sdk/gitea"
"code.gitea.io/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/util"
)
// CommentType defines whether a comment is just a simple comment, an action (like close) or a reference.
type CommentType int
// define unknown comment type
const (
CommentTypeUnknown CommentType = -1
)
// Enumerate all the comment types
const (
// Plain comment, can be associated with a commit (CommitID > 0) and a line (LineNum > 0)
CommentTypeComment CommentType = iota
CommentTypeReopen
CommentTypeClose
// References.
CommentTypeIssueRef
// Reference from a commit (not part of a pull request)
CommentTypeCommitRef
// Reference from a comment
CommentTypeCommentRef
// Reference from a pull request
CommentTypePullRef
// Labels changed
CommentTypeLabel
// Milestone changed
CommentTypeMilestone
// Assignees changed
CommentTypeAssignees
// Change Title
CommentTypeChangeTitle
// Delete Branch
CommentTypeDeleteBranch
// Start a stopwatch for time tracking
CommentTypeStartTracking
// Stop a stopwatch for time tracking
CommentTypeStopTracking
// Add time manual for time tracking
CommentTypeAddTimeManual
// Cancel a stopwatch for time tracking
CommentTypeCancelTracking
// Added a due date
CommentTypeAddedDeadline
// Modified the due date
CommentTypeModifiedDeadline
// Removed a due date
CommentTypeRemovedDeadline
)
// CommentTag defines comment tag type
type CommentTag int
// Enumerate all the comment tag types
const (
CommentTagNone CommentTag = iota
CommentTagPoster
CommentTagWriter
CommentTagOwner
)
// Comment represents a comment in commit and issue page.
type Comment struct {
ID int64 `xorm:"pk autoincr"`
Type CommentType
PosterID int64 `xorm:"INDEX"`
Poster *User `xorm:"-"`
IssueID int64 `xorm:"INDEX"`
Issue *Issue `xorm:"-"`
LabelID int64
Label *Label `xorm:"-"`
OldMilestoneID int64
MilestoneID int64
OldMilestone *Milestone `xorm:"-"`
Milestone *Milestone `xorm:"-"`
AssigneeID int64
RemovedAssignee bool
Assignee *User `xorm:"-"`
OldTitle string
NewTitle string
CommitID int64
Line int64
Content string `xorm:"TEXT"`
RenderedContent string `xorm:"-"`
CreatedUnix util.TimeStamp `xorm:"INDEX created"`
UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
// Reference issue in commit message, comments, issues, or pull requests
RefExists bool
RefIssue *Issue
RefComment *Comment
RefMessage string
RefURL string
// the commit SHA for commit refs otherwise a SHA of a unique reference identifier
CommitSHA string `xorm:"VARCHAR(40)"`
Attachments []*Attachment `xorm:"-"`
Reactions ReactionList `xorm:"-"`
// For view issue page.
ShowTag CommentTag `xorm:"-"`
}
// LoadIssue loads issue from database
func (c *Comment) LoadIssue() (err error) {
if c.Issue != nil {
return nil
}
c.Issue, err = GetIssueByID(c.IssueID)
return
}
// AfterLoad is invoked from XORM after setting the values of all fields of this object.
func (c *Comment) AfterLoad(session *xorm.Session) {
var err error
c.Attachments, err = getAttachmentsByCommentID(session, c.ID)
if err != nil {
log.Error(3, "getAttachmentsByCommentID[%d]: %v", c.ID, err)
}
c.Poster, err = getUserByID(session, c.PosterID)
if err != nil {
if IsErrUserNotExist(err) {
c.PosterID = -1
c.Poster = NewGhostUser()
} else {
log.Error(3, "getUserByID[%d]: %v", c.ID, err)
}
}
}
// AfterDelete is invoked from XORM after the object is deleted.
func (c *Comment) AfterDelete() {
if c.ID <= 0 {
return
}
_, err := DeleteAttachmentsByComment(c.ID, true)
if err != nil {
log.Info("Could not delete files for comment %d on issue #%d: %s", c.ID, c.IssueID, err)
}
}
// HTMLURL formats a URL-string to the issue-comment
func (c *Comment) HTMLURL() string {
err := c.LoadIssue()
if err != nil { // Silently dropping errors :unamused:
log.Error(4, "LoadIssue(%d): %v", c.IssueID, err)
return ""
}
return fmt.Sprintf("%s#%s", c.Issue.HTMLURL(), c.HashTag())
}
// IssueURL formats a URL-string to the issue
func (c *Comment) IssueURL() string {
err := c.LoadIssue()
if err != nil { // Silently dropping errors :unamused:
log.Error(4, "LoadIssue(%d): %v", c.IssueID, err)
return ""
}
if c.Issue.IsPull {
return ""
}
return c.Issue.HTMLURL()
}
// PRURL formats a URL-string to the pull-request
func (c *Comment) PRURL() string {
err := c.LoadIssue()
if err != nil { // Silently dropping errors :unamused:
log.Error(4, "LoadIssue(%d): %v", c.IssueID, err)
return ""
}
if !c.Issue.IsPull {
return ""
}
return c.Issue.HTMLURL()
}
// APIFormat converts a Comment to the api.Comment format
func (c *Comment) APIFormat() *api.Comment {
return &api.Comment{
ID: c.ID,
Poster: c.Poster.APIFormat(),
HTMLURL: c.HTMLURL(),
IssueURL: c.IssueURL(),
PRURL: c.PRURL(),
Body: c.Content,
Created: c.CreatedUnix.AsTime(),
Updated: c.UpdatedUnix.AsTime(),
}
}
// CommentHashTag returns unique hash tag for comment id.
func CommentHashTag(id int64) string {
return fmt.Sprintf("issuecomment-%d", id)
}
// HashTag returns unique hash tag for comment.
func (c *Comment) HashTag() string {
return CommentHashTag(c.ID)
}
// EventTag returns unique event hash tag for comment.
func (c *Comment) EventTag() string {
return "event-" + com.ToStr(c.ID)
}
// LoadReference if comment.Type is CommentType{Issue,Commit,Comment,Pull}Ref, then load RefIssue, RefComment
func (c *Comment) LoadReference() error {
if c.Type == CommentTypeIssueRef || c.Type == CommentTypePullRef {
issueID := int64(0)
n, err := fmt.Sscanf(c.Content, "%d", &issueID)
if err != nil {
return err
}
if n == 1 {
refIssue, err := GetIssueByID(issueID)
if err != nil {
return err
}
pullOrIssue := "issues"
if refIssue.IsPull {
pullOrIssue = "pulls"
}
c.RefIssue = refIssue
c.RefURL = fmt.Sprintf("%s/%s/%d", refIssue.Repo.Link(), pullOrIssue, refIssue.Index)
c.RefExists = true
}
} else if c.Type == CommentTypeCommitRef {
if strings.HasPrefix(c.Content, `<a href="`) && strings.HasSuffix(c.Content, `</a>`) {
// this is an old style commit ref
content := strings.TrimSuffix(strings.TrimPrefix(c.Content, `<a href="`), `</a>`)
contentParts := strings.SplitN(content, `">`, 2)
if len(contentParts) == 2 {
c.RefURL = contentParts[0]
c.RefMessage = contentParts[1]
c.RefExists = true
}
} else {
// this is a new style commit ref
contentParts := strings.SplitN(c.Content, " ", 2)
if len(contentParts) == 2 {
repoID := int64(0)
n, err := fmt.Sscanf(contentParts[0], "%d", &repoID)
if err != nil {
return err
}
if n == 1 {
refRepo, err := GetRepositoryByID(repoID)
if err != nil {
return err
}
gitRepo, err := git.OpenRepository(refRepo.RepoPath())
if err != nil {
return err
}
refCommit, err := gitRepo.GetCommit(contentParts[1][:40])
if err != nil {
return err
}
c.RefURL = fmt.Sprintf("%s/commit/%s", refRepo.Link(), refCommit.ID.String())
c.RefMessage = refCommit.CommitMessage
c.RefExists = true
}
}
}
} else if c.Type == CommentTypeCommentRef {
commentID := int64(0)
n, err := fmt.Sscanf(c.Content, "%d", &commentID)
if err != nil {
return err
}
if n == 1 {
refComment, err := GetCommentByID(commentID)
if err != nil {
return err
}
refIssue, err := GetIssueByID(refComment.IssueID)
if err != nil {
return err
}
pullOrIssue := "issues"
if refIssue.IsPull {
pullOrIssue = "pulls"
}
c.RefIssue = refIssue
c.RefComment = refComment
c.RefURL = fmt.Sprintf("%s/%s/%d#%s", refIssue.Repo.Link(), pullOrIssue, refIssue.Index, refComment.HashTag())
c.RefExists = true
}
}
return nil
}
// LoadLabel if comment.Type is CommentTypeLabel, then load Label
func (c *Comment) LoadLabel() error {
var label Label
has, err := x.ID(c.LabelID).Get(&label)
if err != nil {
return err
} else if has {
c.Label = &label
} else {
// Ignore Label is deleted, but not clear this table
log.Warn("Commit %d cannot load label %d", c.ID, c.LabelID)
}
return nil
}
// LoadMilestone if comment.Type is CommentTypeMilestone, then load milestone
func (c *Comment) LoadMilestone() error {
if c.OldMilestoneID > 0 {
var oldMilestone Milestone
has, err := x.ID(c.OldMilestoneID).Get(&oldMilestone)
if err != nil {
return err
} else if has {
c.OldMilestone = &oldMilestone
}
}
if c.MilestoneID > 0 {
var milestone Milestone
has, err := x.ID(c.MilestoneID).Get(&milestone)
if err != nil {
return err
} else if has {
c.Milestone = &milestone
}
}
return nil
}
// LoadAssigneeUser if comment.Type is CommentTypeAssignees, then load assignees
func (c *Comment) LoadAssigneeUser() error {
var err error
if c.AssigneeID > 0 {
c.Assignee, err = getUserByID(x, c.AssigneeID)
if err != nil {
if !IsErrUserNotExist(err) {
return err
}
c.Assignee = NewGhostUser()
}
}
return nil
}
// MailParticipants sends new comment emails to repository watchers
// and mentioned people.
func (c *Comment) MailParticipants(e Engine, opType ActionType, issue *Issue) (err error) {
mentions := markup.FindAllMentions(c.Content)
if err = UpdateIssueMentions(e, c.IssueID, mentions); err != nil {
return fmt.Errorf("UpdateIssueMentions [%d]: %v", c.IssueID, err)
}
content := c.Content
switch opType {
case ActionCloseIssue:
content = fmt.Sprintf("Closed #%d", issue.Index)
case ActionReopenIssue:
content = fmt.Sprintf("Reopened #%d", issue.Index)
}
if err = mailIssueCommentToParticipants(e, issue, c.Poster, content, c, mentions); err != nil {
log.Error(4, "mailIssueCommentToParticipants: %v", err)
}
return nil
}
func (c *Comment) loadReactions(e Engine) (err error) {
if c.Reactions != nil {
return nil
}
c.Reactions, err = findReactions(e, FindReactionsOptions{
IssueID: c.IssueID,
CommentID: c.ID,
})
if err != nil {
return err
}
// Load reaction user data
if _, err := c.Reactions.LoadUsers(); err != nil {
return err
}
return nil
}
// LoadReactions loads comment reactions
func (c *Comment) LoadReactions() error {
return c.loadReactions(x)
}
func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err error) {
var LabelID int64
if opts.Label != nil {
LabelID = opts.Label.ID
}
comment := &Comment{
Type: opts.Type,
PosterID: opts.Doer.ID,
Poster: opts.Doer,
IssueID: opts.Issue.ID,
LabelID: LabelID,
OldMilestoneID: opts.OldMilestoneID,
MilestoneID: opts.MilestoneID,
RemovedAssignee: opts.RemovedAssignee,
AssigneeID: opts.AssigneeID,
CommitID: opts.CommitID,
CommitSHA: opts.CommitSHA,
Line: opts.LineNum,
Content: opts.Content,
OldTitle: opts.OldTitle,
NewTitle: opts.NewTitle,
}
if _, err = e.Insert(comment); err != nil {
return nil, err
}
if err = opts.Repo.getOwner(e); err != nil {
return nil, err
}
// Compose comment action, could be plain comment, close or reopen issue/pull request.
// This object will be used to notify watchers in the end of function.
act := &Action{
ActUserID: opts.Doer.ID,
ActUser: opts.Doer,
Content: fmt.Sprintf("%d|%s", opts.Issue.Index, strings.Split(opts.Content, "\n")[0]),
RepoID: opts.Repo.ID,
Repo: opts.Repo,
Comment: comment,
CommentID: comment.ID,
IsPrivate: opts.Repo.IsPrivate,
}
// Check comment type.
switch opts.Type {
case CommentTypeComment:
act.OpType = ActionCommentIssue
if _, err = e.Exec("UPDATE `issue` SET num_comments=num_comments+1 WHERE id=?", opts.Issue.ID); err != nil {
return nil, err
}
// Check attachments
attachments := make([]*Attachment, 0, len(opts.Attachments))
for _, uuid := range opts.Attachments {
attach, err := getAttachmentByUUID(e, uuid)
if err != nil {
if IsErrAttachmentNotExist(err) {
continue
}
return nil, fmt.Errorf("getAttachmentByUUID [%s]: %v", uuid, err)
}
attachments = append(attachments, attach)
}
for i := range attachments {
attachments[i].IssueID = opts.Issue.ID
attachments[i].CommentID = comment.ID
// No assign value could be 0, so ignore AllCols().
if _, err = e.ID(attachments[i].ID).Update(attachments[i]); err != nil {
return nil, fmt.Errorf("update attachment [%d]: %v", attachments[i].ID, err)
}
}
case CommentTypeReopen:
act.OpType = ActionReopenIssue
if opts.Issue.IsPull {
act.OpType = ActionReopenPullRequest
}
if opts.Issue.IsPull {
_, err = e.Exec("UPDATE `repository` SET num_closed_pulls=num_closed_pulls-1 WHERE id=?", opts.Repo.ID)
} else {
_, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues-1 WHERE id=?", opts.Repo.ID)
}
if err != nil {
return nil, err
}
case CommentTypeClose:
act.OpType = ActionCloseIssue
if opts.Issue.IsPull {
act.OpType = ActionClosePullRequest
}
if opts.Issue.IsPull {
_, err = e.Exec("UPDATE `repository` SET num_closed_pulls=num_closed_pulls+1 WHERE id=?", opts.Repo.ID)
} else {
_, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues+1 WHERE id=?", opts.Repo.ID)
}
if err != nil {
return nil, err
}
}
// update the issue's updated_unix column
if err = updateIssueCols(e, opts.Issue, "updated_unix"); err != nil {
return nil, err
}
// Notify watchers for whatever action comes in, ignore if no action type.
if act.OpType > 0 {
if err = notifyWatchers(e, act); err != nil {
log.Error(4, "notifyWatchers: %v", err)
}
if err = comment.MailParticipants(e, act.OpType, opts.Issue); err != nil {
log.Error(4, "MailParticipants: %v", err)
}
}
return comment, nil
}
func createStatusComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue) (*Comment, error) {
cmtType := CommentTypeClose
if !issue.IsClosed {
cmtType = CommentTypeReopen
}
return createComment(e, &CreateCommentOptions{
Type: cmtType,
Doer: doer,
Repo: repo,
Issue: issue,
})
}
func createLabelComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue, label *Label, add bool) (*Comment, error) {
var content string
if add {
content = "1"
}
return createComment(e, &CreateCommentOptions{
Type: CommentTypeLabel,
Doer: doer,
Repo: repo,
Issue: issue,
Label: label,
Content: content,
})
}
func createMilestoneComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue, oldMilestoneID, milestoneID int64) (*Comment, error) {
return createComment(e, &CreateCommentOptions{
Type: CommentTypeMilestone,
Doer: doer,
Repo: repo,
Issue: issue,
OldMilestoneID: oldMilestoneID,
MilestoneID: milestoneID,
})
}
func createAssigneeComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue, assigneeID int64, removedAssignee bool) (*Comment, error) {
return createComment(e, &CreateCommentOptions{
Type: CommentTypeAssignees,
Doer: doer,
Repo: repo,
Issue: issue,
RemovedAssignee: removedAssignee,
AssigneeID: assigneeID,
})
}
func createDeadlineComment(e *xorm.Session, doer *User, issue *Issue, newDeadlineUnix util.TimeStamp) (*Comment, error) {
var content string
var commentType CommentType
// newDeadline = 0 means deleting
if newDeadlineUnix == 0 {
commentType = CommentTypeRemovedDeadline
content = issue.DeadlineUnix.Format("2006-01-02")
} else if issue.DeadlineUnix == 0 {
// Check if the new date was added or modified
// If the actual deadline is 0 => deadline added
commentType = CommentTypeAddedDeadline
content = newDeadlineUnix.Format("2006-01-02")
} else { // Otherwise modified
commentType = CommentTypeModifiedDeadline
content = newDeadlineUnix.Format("2006-01-02") + "|" + issue.DeadlineUnix.Format("2006-01-02")
}
return createComment(e, &CreateCommentOptions{
Type: commentType,
Doer: doer,
Repo: issue.Repo,
Issue: issue,
Content: content,
})
}
func createChangeTitleComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue, oldTitle, newTitle string) (*Comment, error) {
return createComment(e, &CreateCommentOptions{
Type: CommentTypeChangeTitle,
Doer: doer,
Repo: repo,
Issue: issue,
OldTitle: oldTitle,
NewTitle: newTitle,
})
}
func createDeleteBranchComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue, branchName string) (*Comment, error) {
return createComment(e, &CreateCommentOptions{
Type: CommentTypeDeleteBranch,
Doer: doer,
Repo: repo,
Issue: issue,
CommitSHA: branchName,
})
}
// CreateCommentOptions defines options for creating comment
type CreateCommentOptions struct {
Type CommentType
Doer *User
Repo *Repository
Issue *Issue
Label *Label
OldMilestoneID int64
MilestoneID int64
AssigneeID int64
RemovedAssignee bool
OldTitle string
NewTitle string
CommitID int64
CommitSHA string
LineNum int64
Content string
Attachments []string // UUIDs of attachments
}
// CreateComment creates comment of issue or commit.
func CreateComment(opts *CreateCommentOptions) (comment *Comment, err error) {
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return nil, err
}
comment, err = createComment(sess, opts)
if err != nil {
return nil, err
}
if err = sess.Commit(); err != nil {
return nil, err
}
if opts.Type == CommentTypeComment {
UpdateIssueIndexer(opts.Issue.ID)
if err = CreateOrUpdateCommentAction(comment.Poster, opts.Repo, opts.Issue, comment); err != nil {
return nil, err
}
}
return comment, nil
}
// CreateIssueComment creates a plain issue comment.
func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content string, attachments []string) (*Comment, error) {
comment, err := CreateComment(&CreateCommentOptions{
Type: CommentTypeComment,
Doer: doer,
Repo: repo,
Issue: issue,
Content: content,
Attachments: attachments,
})
if err != nil {
return nil, fmt.Errorf("CreateComment: %v", err)
}
mode, _ := AccessLevel(doer.ID, repo)
if err = PrepareWebhooks(repo, HookEventIssueComment, &api.IssueCommentPayload{
Action: api.HookIssueCommentCreated,
Issue: issue.APIFormat(),
Comment: comment.APIFormat(),
Repository: repo.APIFormat(mode),
Sender: doer.APIFormat(),
}); err != nil {
log.Error(2, "PrepareWebhooks [comment_id: %d]: %v", comment.ID, err)
} else {
go HookQueue.Add(repo.ID)
}
return comment, nil
}
// createRefComment creates a commit, comment, issue, or pull request reference comment to issue.
func createRefComment(doer *User, repo *Repository, issue *Issue, content, refSHA string, commentType CommentType) error {
if len(refSHA) == 0 {
return fmt.Errorf("cannot create reference with empty SHA")
}
// Check if same reference from same issue and comment has already existed.
has, err := x.Get(&Comment{
Type: commentType,
IssueID: issue.ID,
CommitSHA: refSHA,
})
if err != nil {
return fmt.Errorf("check reference comment: %v", err)
} else if has {
return nil
}
_, err = CreateComment(&CreateCommentOptions{
Type: commentType,
Doer: doer,
Repo: repo,
Issue: issue,
CommitSHA: refSHA,
Content: content,
})
return err
}
// CreateCommitRefComment creates a commit reference comment to issue.
func CreateCommitRefComment(doer *User, repo *Repository, issue *Issue, content, commitSHA string) error {
return createRefComment(doer, repo, issue, content, commitSHA, CommentTypeCommitRef)
}
// CreateCommentRefComment creates a comment reference comment to issue.
func CreateCommentRefComment(doer *User, repo *Repository, issue *Issue, content, refSHA string) error {
return createRefComment(doer, repo, issue, content, refSHA, CommentTypeCommentRef)
}
// CreateIssueRefComment creates a comment reference comment to issue.
func CreateIssueRefComment(doer *User, repo *Repository, issue *Issue, content, refSHA string) error {
return createRefComment(doer, repo, issue, content, refSHA, CommentTypeIssueRef)
}
// CreatePullRefComment creates a comment reference comment to issue.
func CreatePullRefComment(doer *User, repo *Repository, issue *Issue, content, refSHA string) error {
return createRefComment(doer, repo, issue, content, refSHA, CommentTypePullRef)
}
// GetCommentByID returns the comment by given ID.
func GetCommentByID(id int64) (*Comment, error) {
c := new(Comment)
has, err := x.ID(id).Get(c)
if err != nil {
return nil, err
} else if !has {
return nil, ErrCommentNotExist{id, 0}
}
return c, nil
}
// FindCommentsOptions describes the conditions to Find comments
type FindCommentsOptions struct {
RepoID int64
IssueID int64
Since int64
Type CommentType
}
func (opts *FindCommentsOptions) toConds() builder.Cond {
var cond = builder.NewCond()
if opts.RepoID > 0 {
cond = cond.And(builder.Eq{"issue.repo_id": opts.RepoID})
}
if opts.IssueID > 0 {
cond = cond.And(builder.Eq{"comment.issue_id": opts.IssueID})
}
if opts.Since > 0 {
cond = cond.And(builder.Gte{"comment.updated_unix": opts.Since})
}
if opts.Type != CommentTypeUnknown {
cond = cond.And(builder.Eq{"comment.type": opts.Type})
}
return cond
}
func findComments(e Engine, opts FindCommentsOptions) ([]*Comment, error) {
comments := make([]*Comment, 0, 10)
sess := e.Where(opts.toConds())
if opts.RepoID > 0 {
sess.Join("INNER", "issue", "issue.id = comment.issue_id")
}
return comments, sess.
Asc("comment.created_unix").
Asc("comment.id").
Find(&comments)
}
// FindComments returns all comments according options
func FindComments(opts FindCommentsOptions) ([]*Comment, error) {
return findComments(x, opts)
}
// GetCommentsByIssueID returns all comments of an issue.
func GetCommentsByIssueID(issueID int64) ([]*Comment, error) {
return findComments(x, FindCommentsOptions{
IssueID: issueID,
Type: CommentTypeUnknown,
})
}
// GetCommentsByIssueIDSince returns a list of comments of an issue since a given time point.
func GetCommentsByIssueIDSince(issueID, since int64) ([]*Comment, error) {
return findComments(x, FindCommentsOptions{
IssueID: issueID,
Type: CommentTypeUnknown,
Since: since,
})
}
// GetCommentsByRepoIDSince returns a list of comments for all issues in a repo since a given time point.
func GetCommentsByRepoIDSince(repoID, since int64) ([]*Comment, error) {
return findComments(x, FindCommentsOptions{
RepoID: repoID,
Type: CommentTypeUnknown,
Since: since,
})
}
// UpdateComment updates information of comment.
func UpdateComment(doer *User, c *Comment, oldContent string) error {
if _, err := x.ID(c.ID).AllCols().Update(c); err != nil {
return err
} else if c.Type == CommentTypeComment {
UpdateIssueIndexer(c.IssueID)
issue, err := GetIssueByID(c.IssueID)
if err != nil {
return err
}
if err = CreateOrUpdateCommentAction(c.Poster, issue.Repo, issue, c); err != nil {
return err
}
}
if err := c.LoadIssue(); err != nil {
return err
}
if err := c.Issue.LoadAttributes(); err != nil {
return err
}
mode, _ := AccessLevel(doer.ID, c.Issue.Repo)
if err := PrepareWebhooks(c.Issue.Repo, HookEventIssueComment, &api.IssueCommentPayload{
Action: api.HookIssueCommentEdited,
Issue: c.Issue.APIFormat(),
Comment: c.APIFormat(),
Changes: &api.ChangesPayload{
Body: &api.ChangesFromPayload{
From: oldContent,
},
},
Repository: c.Issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(),
}); err != nil {
log.Error(2, "PrepareWebhooks [comment_id: %d]: %v", c.ID, err)
} else {
go HookQueue.Add(c.Issue.Repo.ID)
}
return nil
}
// DeleteComment deletes the comment
func DeleteComment(doer *User, comment *Comment) error {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
if _, err := sess.Delete(&Comment{
ID: comment.ID,
}); err != nil {
return err
}
if comment.Type == CommentTypeComment {
if _, err := sess.Exec("UPDATE `issue` SET num_comments = num_comments - 1 WHERE id = ?", comment.IssueID); err != nil {
return err
}
}
if _, err := sess.Where("comment_id = ?", comment.ID).Cols("is_deleted").Update(&Action{IsDeleted: true}); err != nil {
return err
}
if err := sess.Commit(); err != nil {
return err
} else if comment.Type == CommentTypeComment {
UpdateIssueIndexer(comment.IssueID)
}
if err := comment.LoadIssue(); err != nil {
return err
}
if err := comment.Issue.LoadAttributes(); err != nil {
return err
}
mode, _ := AccessLevel(doer.ID, comment.Issue.Repo)
if err := PrepareWebhooks(comment.Issue.Repo, HookEventIssueComment, &api.IssueCommentPayload{
Action: api.HookIssueCommentDeleted,
Issue: comment.Issue.APIFormat(),
Comment: comment.APIFormat(),
Repository: comment.Issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(),
}); err != nil {
log.Error(2, "PrepareWebhooks [comment_id: %d]: %v", comment.ID, err)
} else {
go HookQueue.Add(comment.Issue.Repo.ID)
}
return nil
}