return err
}
- return UpdateReleasesMigrationsByType(tp, externalUserID, userID)
+ if err := UpdateReleasesMigrationsByType(tp, externalUserID, userID); err != nil {
+ return err
+ }
+
+ return UpdateReactionsMigrationsByType(tp, externalUserID, userID)
}
if err != nil {
return err
}
+ if err = issue.loadRepo(e); err != nil {
+ return err
+ }
// Load reaction user data
- if _, err := ReactionList(reactions).loadUsers(e); err != nil {
+ if _, err := ReactionList(reactions).loadUsers(e, issue.Repo); err != nil {
return err
}
})
return err
}
+
+// UpdateReactionsMigrationsByType updates all migrated repositories' reactions from gitServiceType to replace originalAuthorID to posterID
+func UpdateReactionsMigrationsByType(gitServiceType structs.GitServiceType, originalAuthorID string, userID int64) error {
+ _, err := x.Table("reaction").
+ Join("INNER", "issue", "issue.id = reaction.issue_id").
+ Where("issue.repo_id IN (SELECT id FROM repository WHERE original_service_type = ?)", gitServiceType).
+ And("reaction.original_author_id = ?", originalAuthorID).
+ Update(map[string]interface{}{
+ "user_id": userID,
+ "original_author": "",
+ "original_author_id": 0,
+ })
+ return err
+}
return err
}
-func (c *Comment) loadReactions(e Engine) (err error) {
+func (c *Comment) loadReactions(e Engine, repo *Repository) (err error) {
if c.Reactions != nil {
return nil
}
return err
}
// Load reaction user data
- if _, err := c.Reactions.LoadUsers(); err != nil {
+ if _, err := c.Reactions.loadUsers(e, repo); err != nil {
return err
}
return nil
}
// LoadReactions loads comment reactions
-func (c *Comment) LoadReactions() error {
- return c.loadReactions(x)
+func (c *Comment) LoadReactions(repo *Repository) error {
+ return c.loadReactions(x, repo)
}
func (c *Comment) loadReview(e Engine) (err error) {
// Reaction represents a reactions on issues and comments.
type Reaction struct {
- ID int64 `xorm:"pk autoincr"`
- Type string `xorm:"INDEX UNIQUE(s) NOT NULL"`
- IssueID int64 `xorm:"INDEX UNIQUE(s) NOT NULL"`
- CommentID int64 `xorm:"INDEX UNIQUE(s)"`
- UserID int64 `xorm:"INDEX UNIQUE(s) NOT NULL"`
- User *User `xorm:"-"`
- CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+ ID int64 `xorm:"pk autoincr"`
+ Type string `xorm:"INDEX UNIQUE(s) NOT NULL"`
+ IssueID int64 `xorm:"INDEX UNIQUE(s) NOT NULL"`
+ CommentID int64 `xorm:"INDEX UNIQUE(s)"`
+ UserID int64 `xorm:"INDEX UNIQUE(s) NOT NULL"`
+ OriginalAuthorID int64 `xorm:"INDEX UNIQUE(s) NOT NULL DEFAULT(0)"`
+ OriginalAuthor string
+ User *User `xorm:"-"`
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
}
// FindReactionsOptions describes the conditions to Find reactions
cond = cond.And(builder.Eq{"reaction.comment_id": 0})
}
if opts.UserID > 0 {
- cond = cond.And(builder.Eq{"reaction.user_id": opts.UserID})
+ cond = cond.And(builder.Eq{
+ "reaction.user_id": opts.UserID,
+ "reaction.original_author_id": 0,
+ })
}
if opts.Reaction != "" {
cond = cond.And(builder.Eq{"reaction.type": opts.Reaction})
if opts.Comment != nil {
reaction.CommentID = opts.Comment.ID
}
- _, err := e.Delete(reaction)
+ _, err := e.Where("original_author_id = 0").Delete(reaction)
return err
}
return false
}
for _, reaction := range list {
- if reaction.UserID == userID {
+ if reaction.OriginalAuthor == "" && reaction.UserID == userID {
return true
}
}
func (list ReactionList) getUserIDs() []int64 {
userIDs := make(map[int64]struct{}, len(list))
for _, reaction := range list {
+ if reaction.OriginalAuthor != "" {
+ continue
+ }
if _, ok := userIDs[reaction.UserID]; !ok {
userIDs[reaction.UserID] = struct{}{}
}
return keysInt64(userIDs)
}
-func (list ReactionList) loadUsers(e Engine) ([]*User, error) {
+func (list ReactionList) loadUsers(e Engine, repo *Repository) ([]*User, error) {
if len(list) == 0 {
return nil, nil
}
}
for _, reaction := range list {
- if user, ok := userMaps[reaction.UserID]; ok {
+ if reaction.OriginalAuthor != "" {
+ reaction.User = NewReplaceUser(fmt.Sprintf("%s(%s)", reaction.OriginalAuthor, repo.OriginalServiceType.Name()))
+ } else if user, ok := userMaps[reaction.UserID]; ok {
reaction.User = user
} else {
reaction.User = NewGhostUser()
}
// LoadUsers loads reactions' all users
-func (list ReactionList) LoadUsers() ([]*User, error) {
- return list.loadUsers(x)
+func (list ReactionList) LoadUsers(repo *Repository) ([]*User, error) {
+ return list.loadUsers(x, repo)
}
// GetFirstUsers returns first reacted user display names separated by comma
user4 := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User)
issue1 := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)
+ repo1 := AssertExistsAndLoadBean(t, &Repository{ID: issue1.RepoID}).(*Repository)
comment1 := AssertExistsAndLoadBean(t, &Comment{ID: 1}).(*Comment)
addReaction(t, user3, issue1, comment1, "heart")
addReaction(t, user4, issue1, comment1, "+1")
- err := comment1.LoadReactions()
+ err := comment1.LoadReactions(repo1)
assert.NoError(t, err)
assert.Len(t, comment1.Reactions, 4)
return err
}
+ for _, reaction := range issue.Reactions {
+ reaction.IssueID = issue.ID
+ }
+ if _, err := sess.Insert(issue.Reactions); err != nil {
+ return err
+ }
+
cols := make([]string, 0)
if !issue.IsPull {
sess.ID(issue.RepoID).Incr("num_issues")
if err := sess.Begin(); err != nil {
return err
}
- if _, err := sess.NoAutoTime().Insert(comments); err != nil {
- return err
+ for _, comment := range comments {
+ if _, err := sess.NoAutoTime().Insert(comment); err != nil {
+ return err
+ }
+
+ for _, reaction := range comment.Reactions {
+ reaction.IssueID = comment.IssueID
+ reaction.CommentID = comment.ID
+ }
+ if _, err := sess.Insert(comment.Reactions); err != nil {
+ return err
+ }
}
+
for issueID := range issueIDs {
if _, err := sess.Exec("UPDATE issue set num_comments = (SELECT count(*) FROM comment WHERE issue_id = ?) WHERE id = ?", issueID, issueID); err != nil {
return err
NewMigration("add is_restricted column for users table", addIsRestricted),
// v122 -> v123
NewMigration("Add Require Signed Commits to ProtectedBranch", addRequireSignedCommits),
+ // v123 -> v124
+ NewMigration("Add original informations for reactions", addReactionOriginals),
}
// Migrate database to current version
--- /dev/null
+// Copyright 2020 The Gitea 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 migrations
+
+import (
+ "xorm.io/xorm"
+)
+
+func addReactionOriginals(x *xorm.Engine) error {
+ type Reaction struct {
+ OriginalAuthorID int64 `xorm:"INDEX NOT NULL DEFAULT(0)"`
+ OriginalAuthor string
+ }
+
+ return x.Sync2(new(Reaction))
+}
}
}
+// NewReplaceUser creates and returns a fake user for external user
+func NewReplaceUser(name string) *User {
+ return &User{
+ ID: -1,
+ Name: name,
+ LowerName: strings.ToLower(name),
+ }
+}
+
// IsGhost check if user is fake user for a deleted account
func (u *User) IsGhost() bool {
if u == nil {
Created time.Time
Updated time.Time
Content string
- Reactions *Reactions
+ Reactions []*Reaction
}
Updated time.Time
Closed *time.Time
Labels []*Label
- Reactions *Reactions
+ Reactions []*Reaction
}
Assignee string
Assignees []string
IsLocked bool
+ Reactions []*Reaction
}
// IsForkPullRequest returns true if the pull request from a forked repository but not the same repository
-// Copyright 2019 The Gitea Authors. All rights reserved.
-// Copyright 2018 Jonas Franz. All rights reserved.
+// Copyright 2020 The Gitea 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 base
-// Reactions represents a summary of reactions.
-type Reactions struct {
- TotalCount int
- PlusOne int
- MinusOne int
- Laugh int
- Confused int
- Heart int
- Hooray int
+// Reaction represents a reaction to an issue/pr/comment.
+type Reaction struct {
+ UserID int64
+ UserName string
+ Content string
}
if issue.Closed != nil {
is.ClosedUnix = timeutil.TimeStamp(issue.Closed.Unix())
}
- // TODO: add reactions
+ // add reactions
+ for _, reaction := range issue.Reactions {
+ userid, ok := g.userMap[reaction.UserID]
+ if !ok && tp != "" {
+ var err error
+ userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", reaction.UserID))
+ if err != nil {
+ log.Error("GetUserIDByExternalUserID: %v", err)
+ }
+ if userid > 0 {
+ g.userMap[reaction.UserID] = userid
+ }
+ }
+ var res = models.Reaction{
+ Type: reaction.Content,
+ CreatedUnix: timeutil.TimeStampNow(),
+ }
+ if userid > 0 {
+ res.UserID = userid
+ } else {
+ res.UserID = g.doer.ID
+ res.OriginalAuthorID = reaction.UserID
+ res.OriginalAuthor = reaction.UserName
+ }
+ is.Reactions = append(is.Reactions, &res)
+ }
iss = append(iss, &is)
}
cm.OriginalAuthorID = comment.PosterID
}
- cms = append(cms, &cm)
+ // add reactions
+ for _, reaction := range comment.Reactions {
+ userid, ok := g.userMap[reaction.UserID]
+ if !ok && tp != "" {
+ var err error
+ userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", reaction.UserID))
+ if err != nil {
+ log.Error("GetUserIDByExternalUserID: %v", err)
+ }
+ if userid > 0 {
+ g.userMap[reaction.UserID] = userid
+ }
+ }
+ var res = models.Reaction{
+ Type: reaction.Content,
+ CreatedUnix: timeutil.TimeStampNow(),
+ }
+ if userid > 0 {
+ res.UserID = userid
+ } else {
+ res.UserID = g.doer.ID
+ res.OriginalAuthorID = reaction.UserID
+ res.OriginalAuthor = reaction.UserName
+ }
+ cm.Reactions = append(cm.Reactions, &res)
+ }
- // TODO: Reactions
+ cms = append(cms, &cm)
}
return models.InsertIssueComments(cms)
UpdatedUnix: timeutil.TimeStamp(pr.Updated.Unix()),
}
+ tp := g.gitServiceType.Name()
+
userid, ok := g.userMap[pr.PosterID]
- if !ok {
+ if !ok && tp != "" {
var err error
- userid, err = models.GetUserIDByExternalUserID("github", fmt.Sprintf("%v", pr.PosterID))
+ userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", pr.PosterID))
if err != nil {
log.Error("GetUserIDByExternalUserID: %v", err)
}
issue.OriginalAuthorID = pr.PosterID
}
+ // add reactions
+ for _, reaction := range pr.Reactions {
+ userid, ok := g.userMap[reaction.UserID]
+ if !ok && tp != "" {
+ var err error
+ userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", reaction.UserID))
+ if err != nil {
+ log.Error("GetUserIDByExternalUserID: %v", err)
+ }
+ if userid > 0 {
+ g.userMap[reaction.UserID] = userid
+ }
+ }
+ var res = models.Reaction{
+ Type: reaction.Content,
+ CreatedUnix: timeutil.TimeStampNow(),
+ }
+ if userid > 0 {
+ res.UserID = userid
+ } else {
+ res.UserID = g.doer.ID
+ res.OriginalAuthorID = reaction.UserID
+ res.OriginalAuthor = reaction.UserName
+ }
+ issue.Reactions = append(issue.Reactions, &res)
+ }
+
var pullRequest = models.PullRequest{
HeadRepoID: g.repo.ID,
HeadBranch: head,
pullRequest.MergerID = g.doer.ID
}
- // TODO: reactions
// TODO: assignees
return &pullRequest, nil
return releases, nil
}
-func convertGithubReactions(reactions *github.Reactions) *base.Reactions {
- return &base.Reactions{
- TotalCount: *reactions.TotalCount,
- PlusOne: *reactions.PlusOne,
- MinusOne: *reactions.MinusOne,
- Laugh: *reactions.Laugh,
- Confused: *reactions.Confused,
- Heart: *reactions.Heart,
- Hooray: *reactions.Hooray,
- }
-}
-
// GetIssues returns issues according start and limit
func (g *GithubDownloaderV3) GetIssues(page, perPage int) ([]*base.Issue, bool, error) {
opt := &github.IssueListByRepoOptions{
for _, l := range issue.Labels {
labels = append(labels, convertGithubLabel(&l))
}
- var reactions *base.Reactions
- if issue.Reactions != nil {
- reactions = convertGithubReactions(issue.Reactions)
- }
var email string
if issue.User.Email != nil {
email = *issue.User.Email
}
+
+ // get reactions
+ var reactions []*base.Reaction
+ for i := 1; ; i++ {
+ g.sleep()
+ res, resp, err := g.client.Reactions.ListIssueReactions(g.ctx, g.repoOwner, g.repoName, issue.GetNumber(), &github.ListOptions{
+ Page: i,
+ PerPage: perPage,
+ })
+ if err != nil {
+ return nil, false, err
+ }
+ g.rate = &resp.Rate
+ if len(res) == 0 {
+ break
+ }
+ for _, reaction := range res {
+ reactions = append(reactions, &base.Reaction{
+ UserID: reaction.User.GetID(),
+ UserName: reaction.User.GetLogin(),
+ Content: reaction.GetContent(),
+ })
+ }
+ }
+
allIssues = append(allIssues, &base.Issue{
Title: *issue.Title,
Number: int64(*issue.Number),
if comment.User.Email != nil {
email = *comment.User.Email
}
- var reactions *base.Reactions
- if comment.Reactions != nil {
- reactions = convertGithubReactions(comment.Reactions)
+
+ // get reactions
+ var reactions []*base.Reaction
+ for i := 1; ; i++ {
+ g.sleep()
+ res, resp, err := g.client.Reactions.ListIssueCommentReactions(g.ctx, g.repoOwner, g.repoName, comment.GetID(), &github.ListOptions{
+ Page: i,
+ PerPage: 100,
+ })
+ if err != nil {
+ return nil, err
+ }
+ g.rate = &resp.Rate
+ if len(res) == 0 {
+ break
+ }
+ for _, reaction := range res {
+ reactions = append(reactions, &base.Reaction{
+ UserID: reaction.User.GetID(),
+ UserName: reaction.User.GetLogin(),
+ Content: reaction.GetContent(),
+ })
+ }
}
allComments = append(allComments, &base.Comment{
IssueIndex: issueNumber,
labels = append(labels, convertGithubLabel(l))
}
- // FIXME: This API missing reactions, we may need another extra request to get reactions
-
var email string
if pr.User.Email != nil {
email = *pr.User.Email
headUserName = *pr.Head.User.Login
}
+ // get reactions
+ var reactions []*base.Reaction
+ for i := 1; ; i++ {
+ g.sleep()
+ res, resp, err := g.client.Reactions.ListIssueReactions(g.ctx, g.repoOwner, g.repoName, pr.GetNumber(), &github.ListOptions{
+ Page: i,
+ PerPage: perPage,
+ })
+ if err != nil {
+ return nil, err
+ }
+ g.rate = &resp.Rate
+ if len(res) == 0 {
+ break
+ }
+ for _, reaction := range res {
+ reactions = append(reactions, &base.Reaction{
+ UserID: reaction.User.GetID(),
+ UserName: reaction.User.GetLogin(),
+ Content: reaction.GetContent(),
+ })
+ }
+ }
+
allPRs = append(allPRs, &base.PullRequest{
Title: *pr.Title,
Number: int64(*pr.Number),
RepoName: *pr.Base.Repo.Name,
OwnerName: *pr.Base.User.Login,
},
- PatchURL: *pr.PatchURL,
+ PatchURL: *pr.PatchURL,
+ Reactions: reactions,
})
}
Description: "Good for newcomers",
},
},
- Reactions: &base.Reactions{
- TotalCount: 1,
- PlusOne: 1,
- MinusOne: 0,
- Laugh: 0,
- Confused: 0,
- Heart: 0,
- Hooray: 0,
+ Reactions: []*base.Reaction{
+ {
+ UserID: 1669571,
+ UserName: "mrsdizzie",
+ Content: "+1",
+ },
},
Closed: &closed1,
},
Description: "This issue or pull request already exists",
},
},
- Reactions: &base.Reactions{
- TotalCount: 6,
- PlusOne: 1,
- MinusOne: 1,
- Laugh: 1,
- Confused: 1,
- Heart: 1,
- Hooray: 1,
+ Reactions: []*base.Reaction{
+ {
+ UserID: 1669571,
+ UserName: "mrsdizzie",
+ Content: "heart",
+ },
+ {
+ UserID: 1669571,
+ UserName: "mrsdizzie",
+ Content: "laugh",
+ },
+ {
+ UserID: 1669571,
+ UserName: "mrsdizzie",
+ Content: "-1",
+ },
+ {
+ UserID: 1669571,
+ UserName: "mrsdizzie",
+ Content: "confused",
+ },
+ {
+ UserID: 1669571,
+ UserName: "mrsdizzie",
+ Content: "hooray",
+ },
+ {
+ UserID: 1669571,
+ UserName: "mrsdizzie",
+ Content: "+1",
+ },
},
Closed: &closed2,
},
Created: time.Date(2019, 11, 12, 21, 0, 13, 0, time.UTC),
Updated: time.Date(2019, 11, 12, 21, 0, 13, 0, time.UTC),
Content: "This is a comment",
- Reactions: &base.Reactions{
- TotalCount: 1,
- PlusOne: 1,
- MinusOne: 0,
- Laugh: 0,
- Confused: 0,
- Heart: 0,
- Hooray: 0,
+ Reactions: []*base.Reaction{
+ {
+ UserID: 1669571,
+ UserName: "mrsdizzie",
+ Content: "+1",
+ },
},
},
{
Created: time.Date(2019, 11, 12, 22, 7, 14, 0, time.UTC),
Updated: time.Date(2019, 11, 12, 22, 7, 14, 0, time.UTC),
Content: "A second comment",
- Reactions: &base.Reactions{
- TotalCount: 0,
- PlusOne: 0,
- MinusOne: 0,
- Laugh: 0,
- Confused: 0,
- Heart: 0,
- Hooray: 0,
- },
+ Reactions: nil,
},
}, comments[:2])
},
Merged: false,
MergeCommitSHA: "565d1208f5fffdc1c5ae1a2436491eb9a5e4ebae",
+ Reactions: []*base.Reaction{
+ {
+ UserID: 81045,
+ UserName: "lunny",
+ Content: "heart",
+ },
+ {
+ UserID: 81045,
+ UserName: "lunny",
+ Content: "+1",
+ },
+ },
},
}, prs)
}
ctx.Error(http.StatusInternalServerError, "FindIssueReactions", err)
return
}
- _, err = reactions.LoadUsers()
+ _, err = reactions.LoadUsers(ctx.Repo.Repository)
if err != nil {
ctx.Error(http.StatusInternalServerError, "ReactionList.LoadUsers()", err)
return
ctx.Error(http.StatusInternalServerError, "FindIssueReactions", err)
return
}
- _, err = reactions.LoadUsers()
+ _, err = reactions.LoadUsers(ctx.Repo.Repository)
if err != nil {
ctx.Error(http.StatusInternalServerError, "ReactionList.LoadUsers()", err)
return
}
// Reload new reactions
comment.Reactions = nil
- if err = comment.LoadReactions(); err != nil {
+ if err = comment.LoadReactions(ctx.Repo.Repository); err != nil {
log.Info("comment.LoadReactions: %s", err)
break
}
// Reload new reactions
comment.Reactions = nil
- if err = comment.LoadReactions(); err != nil {
+ if err = comment.LoadReactions(ctx.Repo.Repository); err != nil {
log.Info("comment.LoadReactions: %s", err)
break
}