"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/container"
)
// ActionList defines a list of actions
userIDs[action.ActUserID] = struct{}{}
}
}
- return keysInt64(userIDs)
+ return container.KeysInt64(userIDs)
}
func (actions ActionList) loadUsers(e db.Engine) (map[int64]*user_model.User, error) {
repoIDs[action.RepoID] = struct{}{}
}
}
- return keysInt64(repoIDs)
+ return container.KeysInt64(repoIDs)
}
func (actions ActionList) loadRepositories(e db.Engine) error {
return fmt.Sprintf("Pull request [%d] %d was already closed", err.ID, err.Index)
}
-// ErrForbiddenIssueReaction is used when a forbidden reaction was try to created
-type ErrForbiddenIssueReaction struct {
- Reaction string
-}
-
-// IsErrForbiddenIssueReaction checks if an error is a ErrForbiddenIssueReaction.
-func IsErrForbiddenIssueReaction(err error) bool {
- _, ok := err.(ErrForbiddenIssueReaction)
- return ok
-}
-
-func (err ErrForbiddenIssueReaction) Error() string {
- return fmt.Sprintf("'%s' is not an allowed reaction", err.Reaction)
-}
-
-// ErrReactionAlreadyExist is used when a existing reaction was try to created
-type ErrReactionAlreadyExist struct {
- Reaction string
-}
-
-// IsErrReactionAlreadyExist checks if an error is a ErrReactionAlreadyExist.
-func IsErrReactionAlreadyExist(err error) bool {
- _, ok := err.(ErrReactionAlreadyExist)
- return ok
-}
-
-func (err ErrReactionAlreadyExist) Error() string {
- return fmt.Sprintf("reaction '%s' already exists", err.Reaction)
-}
-
// __________ .__ .__ __________ __
// \______ \__ __| | | |\______ \ ____ ________ __ ____ _______/ |_
// | ___/ | \ | | | | _// __ \/ ____/ | \_/ __ \ / ___/\ __\
import (
repo_model "code.gitea.io/gitea/models/repo"
- user_model "code.gitea.io/gitea/models/user"
)
-func keysInt64(m map[int64]struct{}) []int64 {
- keys := make([]int64, 0, len(m))
- for k := range m {
- keys = append(keys, k)
- }
- return keys
-}
-
func valuesRepository(m map[int64]*repo_model.Repository) []*repo_model.Repository {
values := make([]*repo_model.Repository, 0, len(m))
for _, v := range m {
}
return values
}
-
-func valuesUser(m map[int64]*user_model.User) []*user_model.User {
- values := make([]*user_model.User, 0, len(m))
- for _, v := range m {
- values = append(values, v)
- }
- return values
-}
Attachments []*repo_model.Attachment `xorm:"-"`
Comments []*Comment `xorm:"-"`
- Reactions ReactionList `xorm:"-"`
+ Reactions issues.ReactionList `xorm:"-"`
TotalTrackedTime int64 `xorm:"-"`
Assignees []*user_model.User `xorm:"-"`
ForeignReference *foreignreference.ForeignReference `xorm:"-"`
if issue.Reactions != nil {
return nil
}
- e := db.GetEngine(ctx)
- reactions, _, err := findReactions(e, FindReactionsOptions{
+ reactions, _, err := issues.FindReactions(ctx, issues.FindReactionsOptions{
IssueID: issue.ID,
})
if err != nil {
return err
}
// Load reaction user data
- if _, err := ReactionList(reactions).loadUsers(e, issue.Repo); err != nil {
+ if _, err := issues.ReactionList(reactions).LoadUsers(ctx, issue.Repo); err != nil {
return err
}
&IssueAssignees{},
&IssueUser{},
&Notification{},
- &Reaction{},
+ &issues.Reaction{},
&IssueWatch{},
&Stopwatch{},
&TrackedTime{},
}
if _, err = sess.In("issue_id", deleteCond).
- Delete(&Reaction{}); err != nil {
+ Delete(&issues.Reaction{}); err != nil {
return
}
CommitSHA string `xorm:"VARCHAR(40)"`
Attachments []*repo_model.Attachment `xorm:"-"`
- Reactions ReactionList `xorm:"-"`
+ Reactions issues.ReactionList `xorm:"-"`
// For view issue page.
ShowRole RoleDescriptor `xorm:"-"`
return err
}
-func (c *Comment) loadReactions(e db.Engine, repo *repo_model.Repository) (err error) {
+func (c *Comment) loadReactions(ctx context.Context, repo *repo_model.Repository) (err error) {
if c.Reactions != nil {
return nil
}
- c.Reactions, _, err = findReactions(e, FindReactionsOptions{
+ c.Reactions, _, err = issues.FindReactions(ctx, issues.FindReactionsOptions{
IssueID: c.IssueID,
CommentID: c.ID,
})
return err
}
// Load reaction user data
- if _, err := c.Reactions.loadUsers(e, repo); err != nil {
+ if _, err := c.Reactions.LoadUsers(ctx, repo); err != nil {
return err
}
return nil
// LoadReactions loads comment reactions
func (c *Comment) LoadReactions(repo *repo_model.Repository) error {
- return c.loadReactions(db.GetEngine(db.DefaultContext), repo)
+ return c.loadReactions(db.DefaultContext, repo)
}
func (c *Comment) loadReview(e db.Engine) (err error) {
}
defer committer.Close()
- if err := deleteComment(db.GetEngine(ctx), comment); err != nil {
+ if err := deleteComment(ctx, comment); err != nil {
return err
}
return committer.Commit()
}
-func deleteComment(e db.Engine, comment *Comment) error {
+func deleteComment(ctx context.Context, comment *Comment) error {
+ e := db.GetEngine(ctx)
if _, err := e.ID(comment.ID).NoAutoCondition().Delete(comment); err != nil {
return err
}
return err
}
- return deleteReaction(e, &ReactionOptions{Comment: comment})
+ return issues.DeleteReaction(ctx, &issues.ReactionOptions{CommentID: comment.ID})
}
// CodeComments represents comments on code by using this structure: FILENAME -> LINE (+ == proposed; - == previous) -> COMMENTS
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/container"
)
// CommentList defines a list of comments
posterIDs[comment.PosterID] = struct{}{}
}
}
- return keysInt64(posterIDs)
+ return container.KeysInt64(posterIDs)
}
func (comments CommentList) loadPosters(e db.Engine) error {
ids[comment.LabelID] = struct{}{}
}
}
- return keysInt64(ids)
+ return container.KeysInt64(ids)
}
func (comments CommentList) loadLabels(e db.Engine) error {
ids[comment.MilestoneID] = struct{}{}
}
}
- return keysInt64(ids)
+ return container.KeysInt64(ids)
}
func (comments CommentList) loadMilestones(e db.Engine) error {
ids[comment.OldMilestoneID] = struct{}{}
}
}
- return keysInt64(ids)
+ return container.KeysInt64(ids)
}
func (comments CommentList) loadOldMilestones(e db.Engine) error {
ids[comment.AssigneeID] = struct{}{}
}
}
- return keysInt64(ids)
+ return container.KeysInt64(ids)
}
func (comments CommentList) loadAssignees(e db.Engine) error {
ids[comment.IssueID] = struct{}{}
}
}
- return keysInt64(ids)
+ return container.KeysInt64(ids)
}
// Issues returns all the issues of comments
ids[comment.DependentIssueID] = struct{}{}
}
}
- return keysInt64(ids)
+ return container.KeysInt64(ids)
}
func (comments CommentList) loadDependentIssues(ctx context.Context) error {
ids[comment.ReviewID] = struct{}{}
}
}
- return keysInt64(ids)
+ return container.KeysInt64(ids)
}
func (comments CommentList) loadReviews(e db.Engine) error {
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/container"
"xorm.io/builder"
)
repoIDs[issue.RepoID] = struct{}{}
}
}
- return keysInt64(repoIDs)
+ return container.KeysInt64(repoIDs)
}
func (issues IssueList) loadRepositories(e db.Engine) ([]*repo_model.Repository, error) {
posterIDs[issue.PosterID] = struct{}{}
}
}
- return keysInt64(posterIDs)
+ return container.KeysInt64(posterIDs)
}
func (issues IssueList) loadPosters(e db.Engine) error {
ids[issue.MilestoneID] = struct{}{}
}
}
- return keysInt64(ids)
+ return container.KeysInt64(ids)
}
func (issues IssueList) loadMilestones(e db.Engine) error {
+++ /dev/null
-// Copyright 2017 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 models
-
-import (
- "bytes"
- "fmt"
-
- "code.gitea.io/gitea/models/db"
- repo_model "code.gitea.io/gitea/models/repo"
- user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/timeutil"
-
- "xorm.io/builder"
-)
-
-// 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"`
- OriginalAuthorID int64 `xorm:"INDEX UNIQUE(s) NOT NULL DEFAULT(0)"`
- OriginalAuthor string `xorm:"INDEX UNIQUE(s)"`
- User *user_model.User `xorm:"-"`
- CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
-}
-
-func init() {
- db.RegisterModel(new(Reaction))
-}
-
-// FindReactionsOptions describes the conditions to Find reactions
-type FindReactionsOptions struct {
- db.ListOptions
- IssueID int64
- CommentID int64
- UserID int64
- Reaction string
-}
-
-func (opts *FindReactionsOptions) toConds() builder.Cond {
- // If Issue ID is set add to Query
- cond := builder.NewCond()
- if opts.IssueID > 0 {
- cond = cond.And(builder.Eq{"reaction.issue_id": opts.IssueID})
- }
- // If CommentID is > 0 add to Query
- // If it is 0 Query ignore CommentID to select
- // If it is -1 it explicit search of Issue Reactions where CommentID = 0
- if opts.CommentID > 0 {
- cond = cond.And(builder.Eq{"reaction.comment_id": opts.CommentID})
- } else if opts.CommentID == -1 {
- cond = cond.And(builder.Eq{"reaction.comment_id": 0})
- }
- if opts.UserID > 0 {
- 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})
- }
-
- return cond
-}
-
-// FindCommentReactions returns a ReactionList of all reactions from an comment
-func FindCommentReactions(comment *Comment) (ReactionList, int64, error) {
- return findReactions(db.GetEngine(db.DefaultContext), FindReactionsOptions{
- IssueID: comment.IssueID,
- CommentID: comment.ID,
- })
-}
-
-// FindIssueReactions returns a ReactionList of all reactions from an issue
-func FindIssueReactions(issue *Issue, listOptions db.ListOptions) (ReactionList, int64, error) {
- return findReactions(db.GetEngine(db.DefaultContext), FindReactionsOptions{
- ListOptions: listOptions,
- IssueID: issue.ID,
- CommentID: -1,
- })
-}
-
-func findReactions(e db.Engine, opts FindReactionsOptions) ([]*Reaction, int64, error) {
- sess := e.
- Where(opts.toConds()).
- In("reaction.`type`", setting.UI.Reactions).
- Asc("reaction.issue_id", "reaction.comment_id", "reaction.created_unix", "reaction.id")
- if opts.Page != 0 {
- sess = db.SetSessionPagination(sess, &opts)
-
- reactions := make([]*Reaction, 0, opts.PageSize)
- count, err := sess.FindAndCount(&reactions)
- return reactions, count, err
- }
-
- reactions := make([]*Reaction, 0, 10)
- count, err := sess.FindAndCount(&reactions)
- return reactions, count, err
-}
-
-func createReaction(e db.Engine, opts *ReactionOptions) (*Reaction, error) {
- reaction := &Reaction{
- Type: opts.Type,
- UserID: opts.Doer.ID,
- IssueID: opts.Issue.ID,
- }
- findOpts := FindReactionsOptions{
- IssueID: opts.Issue.ID,
- CommentID: -1, // reaction to issue only
- Reaction: opts.Type,
- UserID: opts.Doer.ID,
- }
- if opts.Comment != nil {
- reaction.CommentID = opts.Comment.ID
- findOpts.CommentID = opts.Comment.ID
- }
-
- existingR, _, err := findReactions(e, findOpts)
- if err != nil {
- return nil, err
- }
- if len(existingR) > 0 {
- return existingR[0], ErrReactionAlreadyExist{Reaction: opts.Type}
- }
-
- if _, err := e.Insert(reaction); err != nil {
- return nil, err
- }
-
- return reaction, nil
-}
-
-// ReactionOptions defines options for creating or deleting reactions
-type ReactionOptions struct {
- Type string
- Doer *user_model.User
- Issue *Issue
- Comment *Comment
-}
-
-// CreateReaction creates reaction for issue or comment.
-func CreateReaction(opts *ReactionOptions) (*Reaction, error) {
- if !setting.UI.ReactionsMap[opts.Type] {
- return nil, ErrForbiddenIssueReaction{opts.Type}
- }
-
- ctx, committer, err := db.TxContext()
- if err != nil {
- return nil, err
- }
- defer committer.Close()
-
- reaction, err := createReaction(db.GetEngine(ctx), opts)
- if err != nil {
- return reaction, err
- }
-
- if err := committer.Commit(); err != nil {
- return nil, err
- }
- return reaction, nil
-}
-
-// CreateIssueReaction creates a reaction on issue.
-func CreateIssueReaction(doer *user_model.User, issue *Issue, content string) (*Reaction, error) {
- return CreateReaction(&ReactionOptions{
- Type: content,
- Doer: doer,
- Issue: issue,
- })
-}
-
-// CreateCommentReaction creates a reaction on comment.
-func CreateCommentReaction(doer *user_model.User, issue *Issue, comment *Comment, content string) (*Reaction, error) {
- return CreateReaction(&ReactionOptions{
- Type: content,
- Doer: doer,
- Issue: issue,
- Comment: comment,
- })
-}
-
-func deleteReaction(e db.Engine, opts *ReactionOptions) error {
- reaction := &Reaction{
- Type: opts.Type,
- }
- if opts.Doer != nil {
- reaction.UserID = opts.Doer.ID
- }
- if opts.Issue != nil {
- reaction.IssueID = opts.Issue.ID
- }
- if opts.Comment != nil {
- reaction.CommentID = opts.Comment.ID
- }
- _, err := e.Where("original_author_id = 0").Delete(reaction)
- return err
-}
-
-// DeleteReaction deletes reaction for issue or comment.
-func DeleteReaction(opts *ReactionOptions) error {
- ctx, committer, err := db.TxContext()
- if err != nil {
- return err
- }
- defer committer.Close()
-
- if err := deleteReaction(db.GetEngine(ctx), opts); err != nil {
- return err
- }
-
- return committer.Commit()
-}
-
-// DeleteIssueReaction deletes a reaction on issue.
-func DeleteIssueReaction(doer *user_model.User, issue *Issue, content string) error {
- return DeleteReaction(&ReactionOptions{
- Type: content,
- Doer: doer,
- Issue: issue,
- })
-}
-
-// DeleteCommentReaction deletes a reaction on comment.
-func DeleteCommentReaction(doer *user_model.User, issue *Issue, comment *Comment, content string) error {
- return DeleteReaction(&ReactionOptions{
- Type: content,
- Doer: doer,
- Issue: issue,
- Comment: comment,
- })
-}
-
-// LoadUser load user of reaction
-func (r *Reaction) LoadUser() (*user_model.User, error) {
- if r.User != nil {
- return r.User, nil
- }
- user, err := user_model.GetUserByIDCtx(db.DefaultContext, r.UserID)
- if err != nil {
- return nil, err
- }
- r.User = user
- return user, nil
-}
-
-// ReactionList represents list of reactions
-type ReactionList []*Reaction
-
-// HasUser check if user has reacted
-func (list ReactionList) HasUser(userID int64) bool {
- if userID == 0 {
- return false
- }
- for _, reaction := range list {
- if reaction.OriginalAuthor == "" && reaction.UserID == userID {
- return true
- }
- }
- return false
-}
-
-// GroupByType returns reactions grouped by type
-func (list ReactionList) GroupByType() map[string]ReactionList {
- reactions := make(map[string]ReactionList)
- for _, reaction := range list {
- reactions[reaction.Type] = append(reactions[reaction.Type], reaction)
- }
- return reactions
-}
-
-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 db.Engine, repo *repo_model.Repository) ([]*user_model.User, error) {
- if len(list) == 0 {
- return nil, nil
- }
-
- userIDs := list.getUserIDs()
- userMaps := make(map[int64]*user_model.User, len(userIDs))
- err := e.
- In("id", userIDs).
- Find(&userMaps)
- if err != nil {
- return nil, fmt.Errorf("find user: %v", err)
- }
-
- for _, reaction := range list {
- if reaction.OriginalAuthor != "" {
- reaction.User = user_model.NewReplaceUser(fmt.Sprintf("%s(%s)", reaction.OriginalAuthor, repo.OriginalServiceType.Name()))
- } else if user, ok := userMaps[reaction.UserID]; ok {
- reaction.User = user
- } else {
- reaction.User = user_model.NewGhostUser()
- }
- }
- return valuesUser(userMaps), nil
-}
-
-// LoadUsers loads reactions' all users
-func (list ReactionList) LoadUsers(repo *repo_model.Repository) ([]*user_model.User, error) {
- return list.loadUsers(db.GetEngine(db.DefaultContext), repo)
-}
-
-// GetFirstUsers returns first reacted user display names separated by comma
-func (list ReactionList) GetFirstUsers() string {
- var buffer bytes.Buffer
- rem := setting.UI.ReactionMaxUserNum
- for _, reaction := range list {
- if buffer.Len() > 0 {
- buffer.WriteString(", ")
- }
- buffer.WriteString(reaction.User.DisplayName())
- if rem--; rem == 0 {
- break
- }
- }
- return buffer.String()
-}
-
-// GetMoreUserCount returns count of not shown users in reaction tooltip
-func (list ReactionList) GetMoreUserCount() int {
- if len(list) <= setting.UI.ReactionMaxUserNum {
- return 0
- }
- return len(list) - setting.UI.ReactionMaxUserNum
-}
-
-// RemapExternalUser ExternalUserRemappable interface
-func (r *Reaction) RemapExternalUser(externalName string, externalID, userID int64) error {
- r.OriginalAuthor = externalName
- r.OriginalAuthorID = externalID
- r.UserID = userID
- return nil
-}
-
-// GetUserID ExternalUserRemappable interface
-func (r *Reaction) GetUserID() int64 { return r.UserID }
-
-// GetExternalName ExternalUserRemappable interface
-func (r *Reaction) GetExternalName() string { return r.OriginalAuthor }
-
-// GetExternalID ExternalUserRemappable interface
-func (r *Reaction) GetExternalID() int64 { return r.OriginalAuthorID }
+++ /dev/null
-// Copyright 2017 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 models
-
-import (
- "testing"
-
- "code.gitea.io/gitea/models/db"
- repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/models/unittest"
- user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/setting"
-
- "github.com/stretchr/testify/assert"
-)
-
-func addReaction(t *testing.T, doer *user_model.User, issue *Issue, comment *Comment, content string) {
- var reaction *Reaction
- var err error
- if comment == nil {
- reaction, err = CreateIssueReaction(doer, issue, content)
- } else {
- reaction, err = CreateCommentReaction(doer, issue, comment, content)
- }
- assert.NoError(t, err)
- assert.NotNil(t, reaction)
-}
-
-func TestIssueAddReaction(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
-
- issue1 := unittest.AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)
-
- addReaction(t, user1, issue1, nil, "heart")
-
- unittest.AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID})
-}
-
-func TestIssueAddDuplicateReaction(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
-
- issue1 := unittest.AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)
-
- addReaction(t, user1, issue1, nil, "heart")
-
- reaction, err := CreateReaction(&ReactionOptions{
- Doer: user1,
- Issue: issue1,
- Type: "heart",
- })
- assert.Error(t, err)
- assert.Equal(t, ErrReactionAlreadyExist{Reaction: "heart"}, err)
-
- existingR := unittest.AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID}).(*Reaction)
- assert.Equal(t, existingR.ID, reaction.ID)
-}
-
-func TestIssueDeleteReaction(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
-
- issue1 := unittest.AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)
-
- addReaction(t, user1, issue1, nil, "heart")
-
- err := DeleteIssueReaction(user1, issue1, "heart")
- assert.NoError(t, err)
-
- unittest.AssertNotExistsBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID})
-}
-
-func TestIssueReactionCount(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- setting.UI.ReactionMaxUserNum = 2
-
- user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
- user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
- user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User)
- user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}).(*user_model.User)
- ghost := user_model.NewGhostUser()
-
- issue := unittest.AssertExistsAndLoadBean(t, &Issue{ID: 2}).(*Issue)
-
- addReaction(t, user1, issue, nil, "heart")
- addReaction(t, user2, issue, nil, "heart")
- addReaction(t, user3, issue, nil, "heart")
- addReaction(t, user3, issue, nil, "+1")
- addReaction(t, user4, issue, nil, "+1")
- addReaction(t, user4, issue, nil, "heart")
- addReaction(t, ghost, issue, nil, "-1")
-
- err := issue.loadReactions(db.DefaultContext)
- assert.NoError(t, err)
-
- assert.Len(t, issue.Reactions, 7)
-
- reactions := issue.Reactions.GroupByType()
- assert.Len(t, reactions["heart"], 4)
- assert.Equal(t, 2, reactions["heart"].GetMoreUserCount())
- assert.Equal(t, user1.DisplayName()+", "+user2.DisplayName(), reactions["heart"].GetFirstUsers())
- assert.True(t, reactions["heart"].HasUser(1))
- assert.False(t, reactions["heart"].HasUser(5))
- assert.False(t, reactions["heart"].HasUser(0))
- assert.Len(t, reactions["+1"], 2)
- assert.Equal(t, 0, reactions["+1"].GetMoreUserCount())
- assert.Len(t, reactions["-1"], 1)
-}
-
-func TestIssueCommentAddReaction(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
-
- issue1 := unittest.AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)
-
- comment1 := unittest.AssertExistsAndLoadBean(t, &Comment{ID: 1}).(*Comment)
-
- addReaction(t, user1, issue1, comment1, "heart")
-
- unittest.AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID, CommentID: comment1.ID})
-}
-
-func TestIssueCommentDeleteReaction(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
- user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
- user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User)
- user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}).(*user_model.User)
-
- issue1 := unittest.AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)
- repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue1.RepoID}).(*repo_model.Repository)
-
- comment1 := unittest.AssertExistsAndLoadBean(t, &Comment{ID: 1}).(*Comment)
-
- addReaction(t, user1, issue1, comment1, "heart")
- addReaction(t, user2, issue1, comment1, "heart")
- addReaction(t, user3, issue1, comment1, "heart")
- addReaction(t, user4, issue1, comment1, "+1")
-
- err := comment1.LoadReactions(repo1)
- assert.NoError(t, err)
- assert.Len(t, comment1.Reactions, 4)
-
- reactions := comment1.Reactions.GroupByType()
- assert.Len(t, reactions["heart"], 3)
- assert.Len(t, reactions["+1"], 1)
-}
-
-func TestIssueCommentReactionCount(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
-
- issue1 := unittest.AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)
-
- comment1 := unittest.AssertExistsAndLoadBean(t, &Comment{ID: 1}).(*Comment)
-
- addReaction(t, user1, issue1, comment1, "heart")
- assert.NoError(t, DeleteCommentReaction(user1, issue1, comment1, "heart"))
-
- unittest.AssertNotExistsBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID, CommentID: comment1.ID})
-}
"testing"
"code.gitea.io/gitea/models/unittest"
+ "code.gitea.io/gitea/modules/setting"
)
+func init() {
+ setting.SetCustomPathAndConf("", "", "")
+ setting.LoadForTest()
+}
+
func TestMain(m *testing.M) {
- unittest.MainTest(m, filepath.Join("..", ".."), "")
+ unittest.MainTest(m, filepath.Join("..", ".."),
+ "reaction.yml",
+ "user.yml",
+ "repository.yml",
+ )
}
--- /dev/null
+// Copyright 2017 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 issues
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+
+ "code.gitea.io/gitea/models/db"
+ repo_model "code.gitea.io/gitea/models/repo"
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/container"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/builder"
+)
+
+// ErrForbiddenIssueReaction is used when a forbidden reaction was try to created
+type ErrForbiddenIssueReaction struct {
+ Reaction string
+}
+
+// IsErrForbiddenIssueReaction checks if an error is a ErrForbiddenIssueReaction.
+func IsErrForbiddenIssueReaction(err error) bool {
+ _, ok := err.(ErrForbiddenIssueReaction)
+ return ok
+}
+
+func (err ErrForbiddenIssueReaction) Error() string {
+ return fmt.Sprintf("'%s' is not an allowed reaction", err.Reaction)
+}
+
+// ErrReactionAlreadyExist is used when a existing reaction was try to created
+type ErrReactionAlreadyExist struct {
+ Reaction string
+}
+
+// IsErrReactionAlreadyExist checks if an error is a ErrReactionAlreadyExist.
+func IsErrReactionAlreadyExist(err error) bool {
+ _, ok := err.(ErrReactionAlreadyExist)
+ return ok
+}
+
+func (err ErrReactionAlreadyExist) Error() string {
+ return fmt.Sprintf("reaction '%s' already exists", err.Reaction)
+}
+
+// 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"`
+ OriginalAuthorID int64 `xorm:"INDEX UNIQUE(s) NOT NULL DEFAULT(0)"`
+ OriginalAuthor string `xorm:"INDEX UNIQUE(s)"`
+ User *user_model.User `xorm:"-"`
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+}
+
+// LoadUser load user of reaction
+func (r *Reaction) LoadUser() (*user_model.User, error) {
+ if r.User != nil {
+ return r.User, nil
+ }
+ user, err := user_model.GetUserByIDCtx(db.DefaultContext, r.UserID)
+ if err != nil {
+ return nil, err
+ }
+ r.User = user
+ return user, nil
+}
+
+// RemapExternalUser ExternalUserRemappable interface
+func (r *Reaction) RemapExternalUser(externalName string, externalID, userID int64) error {
+ r.OriginalAuthor = externalName
+ r.OriginalAuthorID = externalID
+ r.UserID = userID
+ return nil
+}
+
+// GetUserID ExternalUserRemappable interface
+func (r *Reaction) GetUserID() int64 { return r.UserID }
+
+// GetExternalName ExternalUserRemappable interface
+func (r *Reaction) GetExternalName() string { return r.OriginalAuthor }
+
+// GetExternalID ExternalUserRemappable interface
+func (r *Reaction) GetExternalID() int64 { return r.OriginalAuthorID }
+
+func init() {
+ db.RegisterModel(new(Reaction))
+}
+
+// FindReactionsOptions describes the conditions to Find reactions
+type FindReactionsOptions struct {
+ db.ListOptions
+ IssueID int64
+ CommentID int64
+ UserID int64
+ Reaction string
+}
+
+func (opts *FindReactionsOptions) toConds() builder.Cond {
+ // If Issue ID is set add to Query
+ cond := builder.NewCond()
+ if opts.IssueID > 0 {
+ cond = cond.And(builder.Eq{"reaction.issue_id": opts.IssueID})
+ }
+ // If CommentID is > 0 add to Query
+ // If it is 0 Query ignore CommentID to select
+ // If it is -1 it explicit search of Issue Reactions where CommentID = 0
+ if opts.CommentID > 0 {
+ cond = cond.And(builder.Eq{"reaction.comment_id": opts.CommentID})
+ } else if opts.CommentID == -1 {
+ cond = cond.And(builder.Eq{"reaction.comment_id": 0})
+ }
+ if opts.UserID > 0 {
+ 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})
+ }
+
+ return cond
+}
+
+// FindCommentReactions returns a ReactionList of all reactions from an comment
+func FindCommentReactions(issueID, commentID int64) (ReactionList, int64, error) {
+ return FindReactions(db.DefaultContext, FindReactionsOptions{
+ IssueID: issueID,
+ CommentID: commentID,
+ })
+}
+
+// FindIssueReactions returns a ReactionList of all reactions from an issue
+func FindIssueReactions(issueID int64, listOptions db.ListOptions) (ReactionList, int64, error) {
+ return FindReactions(db.DefaultContext, FindReactionsOptions{
+ ListOptions: listOptions,
+ IssueID: issueID,
+ CommentID: -1,
+ })
+}
+
+// FindReactions returns a ReactionList of all reactions from an issue or a comment
+func FindReactions(ctx context.Context, opts FindReactionsOptions) (ReactionList, int64, error) {
+ sess := db.GetEngine(ctx).
+ Where(opts.toConds()).
+ In("reaction.`type`", setting.UI.Reactions).
+ Asc("reaction.issue_id", "reaction.comment_id", "reaction.created_unix", "reaction.id")
+ if opts.Page != 0 {
+ sess = db.SetSessionPagination(sess, &opts)
+
+ reactions := make([]*Reaction, 0, opts.PageSize)
+ count, err := sess.FindAndCount(&reactions)
+ return reactions, count, err
+ }
+
+ reactions := make([]*Reaction, 0, 10)
+ count, err := sess.FindAndCount(&reactions)
+ return reactions, count, err
+}
+
+func createReaction(ctx context.Context, opts *ReactionOptions) (*Reaction, error) {
+ reaction := &Reaction{
+ Type: opts.Type,
+ UserID: opts.DoerID,
+ IssueID: opts.IssueID,
+ CommentID: opts.CommentID,
+ }
+ findOpts := FindReactionsOptions{
+ IssueID: opts.IssueID,
+ CommentID: opts.CommentID,
+ Reaction: opts.Type,
+ UserID: opts.DoerID,
+ }
+
+ existingR, _, err := FindReactions(ctx, findOpts)
+ if err != nil {
+ return nil, err
+ }
+ if len(existingR) > 0 {
+ return existingR[0], ErrReactionAlreadyExist{Reaction: opts.Type}
+ }
+
+ if err := db.Insert(ctx, reaction); err != nil {
+ return nil, err
+ }
+
+ return reaction, nil
+}
+
+// ReactionOptions defines options for creating or deleting reactions
+type ReactionOptions struct {
+ Type string
+ DoerID int64
+ IssueID int64
+ CommentID int64
+}
+
+// CreateReaction creates reaction for issue or comment.
+func CreateReaction(opts *ReactionOptions) (*Reaction, error) {
+ if !setting.UI.ReactionsMap[opts.Type] {
+ return nil, ErrForbiddenIssueReaction{opts.Type}
+ }
+
+ ctx, committer, err := db.TxContext()
+ if err != nil {
+ return nil, err
+ }
+ defer committer.Close()
+
+ reaction, err := createReaction(ctx, opts)
+ if err != nil {
+ return reaction, err
+ }
+
+ if err := committer.Commit(); err != nil {
+ return nil, err
+ }
+ return reaction, nil
+}
+
+// CreateIssueReaction creates a reaction on issue.
+func CreateIssueReaction(doerID, issueID int64, content string) (*Reaction, error) {
+ return CreateReaction(&ReactionOptions{
+ Type: content,
+ DoerID: doerID,
+ IssueID: issueID,
+ })
+}
+
+// CreateCommentReaction creates a reaction on comment.
+func CreateCommentReaction(doerID, issueID, commentID int64, content string) (*Reaction, error) {
+ return CreateReaction(&ReactionOptions{
+ Type: content,
+ DoerID: doerID,
+ IssueID: issueID,
+ CommentID: commentID,
+ })
+}
+
+// DeleteReaction deletes reaction for issue or comment.
+func DeleteReaction(ctx context.Context, opts *ReactionOptions) error {
+ reaction := &Reaction{
+ Type: opts.Type,
+ UserID: opts.DoerID,
+ IssueID: opts.IssueID,
+ CommentID: opts.CommentID,
+ }
+
+ _, err := db.GetEngine(ctx).Where("original_author_id = 0").Delete(reaction)
+ return err
+}
+
+// DeleteIssueReaction deletes a reaction on issue.
+func DeleteIssueReaction(doerID, issueID int64, content string) error {
+ return DeleteReaction(db.DefaultContext, &ReactionOptions{
+ Type: content,
+ DoerID: doerID,
+ IssueID: issueID,
+ })
+}
+
+// DeleteCommentReaction deletes a reaction on comment.
+func DeleteCommentReaction(doerID, issueID, commentID int64, content string) error {
+ return DeleteReaction(db.DefaultContext, &ReactionOptions{
+ Type: content,
+ DoerID: doerID,
+ IssueID: issueID,
+ CommentID: commentID,
+ })
+}
+
+// ReactionList represents list of reactions
+type ReactionList []*Reaction
+
+// HasUser check if user has reacted
+func (list ReactionList) HasUser(userID int64) bool {
+ if userID == 0 {
+ return false
+ }
+ for _, reaction := range list {
+ if reaction.OriginalAuthor == "" && reaction.UserID == userID {
+ return true
+ }
+ }
+ return false
+}
+
+// GroupByType returns reactions grouped by type
+func (list ReactionList) GroupByType() map[string]ReactionList {
+ reactions := make(map[string]ReactionList)
+ for _, reaction := range list {
+ reactions[reaction.Type] = append(reactions[reaction.Type], reaction)
+ }
+ return reactions
+}
+
+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 container.KeysInt64(userIDs)
+}
+
+func valuesUser(m map[int64]*user_model.User) []*user_model.User {
+ values := make([]*user_model.User, 0, len(m))
+ for _, v := range m {
+ values = append(values, v)
+ }
+ return values
+}
+
+// LoadUsers loads reactions' all users
+func (list ReactionList) LoadUsers(ctx context.Context, repo *repo_model.Repository) ([]*user_model.User, error) {
+ if len(list) == 0 {
+ return nil, nil
+ }
+
+ userIDs := list.getUserIDs()
+ userMaps := make(map[int64]*user_model.User, len(userIDs))
+ err := db.GetEngine(ctx).
+ In("id", userIDs).
+ Find(&userMaps)
+ if err != nil {
+ return nil, fmt.Errorf("find user: %v", err)
+ }
+
+ for _, reaction := range list {
+ if reaction.OriginalAuthor != "" {
+ reaction.User = user_model.NewReplaceUser(fmt.Sprintf("%s(%s)", reaction.OriginalAuthor, repo.OriginalServiceType.Name()))
+ } else if user, ok := userMaps[reaction.UserID]; ok {
+ reaction.User = user
+ } else {
+ reaction.User = user_model.NewGhostUser()
+ }
+ }
+ return valuesUser(userMaps), nil
+}
+
+// GetFirstUsers returns first reacted user display names separated by comma
+func (list ReactionList) GetFirstUsers() string {
+ var buffer bytes.Buffer
+ rem := setting.UI.ReactionMaxUserNum
+ for _, reaction := range list {
+ if buffer.Len() > 0 {
+ buffer.WriteString(", ")
+ }
+ buffer.WriteString(reaction.User.DisplayName())
+ if rem--; rem == 0 {
+ break
+ }
+ }
+ return buffer.String()
+}
+
+// GetMoreUserCount returns count of not shown users in reaction tooltip
+func (list ReactionList) GetMoreUserCount() int {
+ if len(list) <= setting.UI.ReactionMaxUserNum {
+ return 0
+ }
+ return len(list) - setting.UI.ReactionMaxUserNum
+}
--- /dev/null
+// Copyright 2017 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 issues
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/db"
+ repo_model "code.gitea.io/gitea/models/repo"
+ "code.gitea.io/gitea/models/unittest"
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/setting"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func addReaction(t *testing.T, doerID, issueID, commentID int64, content string) {
+ var reaction *Reaction
+ var err error
+ if commentID == 0 {
+ reaction, err = CreateIssueReaction(doerID, issueID, content)
+ } else {
+ reaction, err = CreateCommentReaction(doerID, issueID, commentID, content)
+ }
+ assert.NoError(t, err)
+ assert.NotNil(t, reaction)
+}
+
+func TestIssueAddReaction(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
+
+ var issue1ID int64 = 1
+
+ addReaction(t, user1.ID, issue1ID, 0, "heart")
+
+ unittest.AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1ID})
+}
+
+func TestIssueAddDuplicateReaction(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
+
+ var issue1ID int64 = 1
+
+ addReaction(t, user1.ID, issue1ID, 0, "heart")
+
+ reaction, err := CreateReaction(&ReactionOptions{
+ DoerID: user1.ID,
+ IssueID: issue1ID,
+ Type: "heart",
+ })
+ assert.Error(t, err)
+ assert.Equal(t, ErrReactionAlreadyExist{Reaction: "heart"}, err)
+
+ existingR := unittest.AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1ID}).(*Reaction)
+ assert.Equal(t, existingR.ID, reaction.ID)
+}
+
+func TestIssueDeleteReaction(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
+
+ var issue1ID int64 = 1
+
+ addReaction(t, user1.ID, issue1ID, 0, "heart")
+
+ err := DeleteIssueReaction(user1.ID, issue1ID, "heart")
+ assert.NoError(t, err)
+
+ unittest.AssertNotExistsBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1ID})
+}
+
+func TestIssueReactionCount(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ setting.UI.ReactionMaxUserNum = 2
+
+ user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
+ user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
+ user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User)
+ user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}).(*user_model.User)
+ ghost := user_model.NewGhostUser()
+
+ var issueID int64 = 2
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
+
+ addReaction(t, user1.ID, issueID, 0, "heart")
+ addReaction(t, user2.ID, issueID, 0, "heart")
+ addReaction(t, user3.ID, issueID, 0, "heart")
+ addReaction(t, user3.ID, issueID, 0, "+1")
+ addReaction(t, user4.ID, issueID, 0, "+1")
+ addReaction(t, user4.ID, issueID, 0, "heart")
+ addReaction(t, ghost.ID, issueID, 0, "-1")
+
+ reactionsList, _, err := FindReactions(db.DefaultContext, FindReactionsOptions{
+ IssueID: issueID,
+ })
+ assert.NoError(t, err)
+ assert.Len(t, reactionsList, 7)
+ _, err = reactionsList.LoadUsers(db.DefaultContext, repo)
+ assert.NoError(t, err)
+
+ reactions := reactionsList.GroupByType()
+ assert.Len(t, reactions["heart"], 4)
+ assert.Equal(t, 2, reactions["heart"].GetMoreUserCount())
+ assert.Equal(t, user1.DisplayName()+", "+user2.DisplayName(), reactions["heart"].GetFirstUsers())
+ assert.True(t, reactions["heart"].HasUser(1))
+ assert.False(t, reactions["heart"].HasUser(5))
+ assert.False(t, reactions["heart"].HasUser(0))
+ assert.Len(t, reactions["+1"], 2)
+ assert.Equal(t, 0, reactions["+1"].GetMoreUserCount())
+ assert.Len(t, reactions["-1"], 1)
+}
+
+func TestIssueCommentAddReaction(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
+
+ var issue1ID int64 = 1
+ var comment1ID int64 = 1
+
+ addReaction(t, user1.ID, issue1ID, comment1ID, "heart")
+
+ unittest.AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1ID, CommentID: comment1ID})
+}
+
+func TestIssueCommentDeleteReaction(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
+ user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
+ user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User)
+ user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}).(*user_model.User)
+
+ var issue1ID int64 = 1
+ var comment1ID int64 = 1
+
+ addReaction(t, user1.ID, issue1ID, comment1ID, "heart")
+ addReaction(t, user2.ID, issue1ID, comment1ID, "heart")
+ addReaction(t, user3.ID, issue1ID, comment1ID, "heart")
+ addReaction(t, user4.ID, issue1ID, comment1ID, "+1")
+
+ reactionsList, _, err := FindReactions(db.DefaultContext, FindReactionsOptions{
+ IssueID: issue1ID,
+ CommentID: comment1ID,
+ })
+ assert.NoError(t, err)
+ assert.Len(t, reactionsList, 4)
+
+ reactions := reactionsList.GroupByType()
+ assert.Len(t, reactions["heart"], 3)
+ assert.Len(t, reactions["+1"], 1)
+}
+
+func TestIssueCommentReactionCount(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
+
+ var issue1ID int64 = 1
+ var comment1ID int64 = 1
+
+ addReaction(t, user1.ID, issue1ID, comment1ID, "heart")
+ assert.NoError(t, DeleteCommentReaction(user1.ID, issue1ID, comment1ID, "heart"))
+
+ unittest.AssertNotExistsBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1ID, CommentID: comment1ID})
+}
"testing"
"code.gitea.io/gitea/models/foreignreference"
+ issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
label := unittest.AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label)
milestone := unittest.AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone)
assert.EqualValues(t, milestone.ID, 1)
- reaction := &Reaction{
+ reaction := &issues_model.Reaction{
Type: "heart",
UserID: owner.ID,
}
Poster: owner,
IsClosed: true,
Labels: []*Label{label},
- Reactions: []*Reaction{reaction},
+ Reactions: []*issues_model.Reaction{reaction},
ForeignReference: &foreignreference.ForeignReference{
ForeignIndex: strconv.FormatInt(foreignIndex, 10),
RepoID: repo.ID,
err = i.LoadAttributes()
assert.NoError(t, err)
assert.EqualValues(t, strconv.FormatInt(foreignIndex, 10), i.ForeignReference.ForeignIndex)
- unittest.AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: owner.ID, IssueID: i.ID})
+ unittest.AssertExistsAndLoadBean(t, &issues_model.Reaction{Type: "heart", UserID: owner.ID, IssueID: i.ID})
}
func TestMigrate_CreateIssuesIsPullFalse(t *testing.T) {
issue := unittest.AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)
_ = issue.LoadRepo()
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}).(*user_model.User)
- reaction := &Reaction{
+ reaction := &issues_model.Reaction{
Type: "heart",
UserID: owner.ID,
}
Poster: owner,
IssueID: issue.ID,
Issue: issue,
- Reactions: []*Reaction{reaction},
+ Reactions: []*issues_model.Reaction{reaction},
}
err := InsertIssueComments([]*Comment{comment})
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
ids[notification.RepoID] = struct{}{}
}
}
- return keysInt64(ids)
+ return container.KeysInt64(ids)
}
// LoadRepos loads repositories from database
ids[notification.IssueID] = struct{}{}
}
}
- return keysInt64(ids)
+ return container.KeysInt64(ids)
}
// LoadIssues loads issues from database
ids[notification.CommentID] = struct{}{}
}
}
- return keysInt64(ids)
+ return container.KeysInt64(ids)
}
// LoadComments loads comments from database
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
users := make(map[int64]*user_model.User, len(set))
if err := e.
Where("id > 0").
- In("id", keysInt64(set)).
+ In("id", container.KeysInt64(set)).
Find(&users); err != nil {
return fmt.Errorf("find users: %v", err)
}
asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
&IssueUser{UID: u.ID},
&user_model.EmailAddress{UID: u.ID},
&user_model.UserOpenID{UID: u.ID},
- &Reaction{UserID: u.ID},
+ &issues.Reaction{UserID: u.ID},
&organization.TeamUser{UID: u.ID},
&Collaboration{UserID: u.ID},
&Stopwatch{UserID: u.ID},
}
for _, comment := range comments {
- if err = deleteComment(e, comment); err != nil {
+ if err = deleteComment(ctx, comment); err != nil {
return err
}
}
}
// Delete Reactions
- if err = deleteReaction(e, &ReactionOptions{Doer: u}); err != nil {
+ if err = issues.DeleteReaction(ctx, &issues.ReactionOptions{DoerID: u.ID}); err != nil {
return err
}
}
--- /dev/null
+// Copyright 2022 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 container
+
+// KeysInt64 returns keys slice for a map with int64 key
+func KeysInt64(m map[int64]struct{}) []int64 {
+ keys := make([]int64, 0, len(m))
+ for k := range m {
+ keys = append(keys, k)
+ }
+ return keys
+}
"net/http"
"code.gitea.io/gitea/models"
+ issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs"
return
}
- reactions, _, err := models.FindCommentReactions(comment)
+ reactions, _, err := issues_model.FindCommentReactions(comment.IssueID, comment.ID)
if err != nil {
ctx.Error(http.StatusInternalServerError, "FindCommentReactions", err)
return
}
- _, err = reactions.LoadUsers(ctx.Repo.Repository)
+ _, err = reactions.LoadUsers(ctx, ctx.Repo.Repository)
if err != nil {
ctx.Error(http.StatusInternalServerError, "ReactionList.LoadUsers()", err)
return
if isCreateType {
// PostIssueCommentReaction part
- reaction, err := models.CreateCommentReaction(ctx.Doer, comment.Issue, comment, form.Reaction)
+ reaction, err := issues_model.CreateCommentReaction(ctx.Doer.ID, comment.Issue.ID, comment.ID, form.Reaction)
if err != nil {
- if models.IsErrForbiddenIssueReaction(err) {
+ if issues_model.IsErrForbiddenIssueReaction(err) {
ctx.Error(http.StatusForbidden, err.Error(), err)
- } else if models.IsErrReactionAlreadyExist(err) {
+ } else if issues_model.IsErrReactionAlreadyExist(err) {
ctx.JSON(http.StatusOK, api.Reaction{
User: convert.ToUser(ctx.Doer, ctx.Doer),
Reaction: reaction.Type,
})
} else {
// DeleteIssueCommentReaction part
- err = models.DeleteCommentReaction(ctx.Doer, comment.Issue, comment, form.Reaction)
+ err = issues_model.DeleteCommentReaction(ctx.Doer.ID, comment.Issue.ID, comment.ID, form.Reaction)
if err != nil {
ctx.Error(http.StatusInternalServerError, "DeleteCommentReaction", err)
return
return
}
- reactions, count, err := models.FindIssueReactions(issue, utils.GetListOptions(ctx))
+ reactions, count, err := issues_model.FindIssueReactions(issue.ID, utils.GetListOptions(ctx))
if err != nil {
ctx.Error(http.StatusInternalServerError, "FindIssueReactions", err)
return
}
- _, err = reactions.LoadUsers(ctx.Repo.Repository)
+ _, err = reactions.LoadUsers(ctx, ctx.Repo.Repository)
if err != nil {
ctx.Error(http.StatusInternalServerError, "ReactionList.LoadUsers()", err)
return
if isCreateType {
// PostIssueReaction part
- reaction, err := models.CreateIssueReaction(ctx.Doer, issue, form.Reaction)
+ reaction, err := issues_model.CreateIssueReaction(ctx.Doer.ID, issue.ID, form.Reaction)
if err != nil {
- if models.IsErrForbiddenIssueReaction(err) {
+ if issues_model.IsErrForbiddenIssueReaction(err) {
ctx.Error(http.StatusForbidden, err.Error(), err)
- } else if models.IsErrReactionAlreadyExist(err) {
+ } else if issues_model.IsErrReactionAlreadyExist(err) {
ctx.JSON(http.StatusOK, api.Reaction{
User: convert.ToUser(ctx.Doer, ctx.Doer),
Reaction: reaction.Type,
})
} else {
// DeleteIssueReaction part
- err = models.DeleteIssueReaction(ctx.Doer, issue, form.Reaction)
+ err = issues_model.DeleteIssueReaction(ctx.Doer.ID, issue.ID, form.Reaction)
if err != nil {
ctx.Error(http.StatusInternalServerError, "DeleteIssueReaction", err)
return
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
+ issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization"
project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo"
switch ctx.Params(":action") {
case "react":
- reaction, err := models.CreateIssueReaction(ctx.Doer, issue, form.Content)
+ reaction, err := issues_model.CreateIssueReaction(ctx.Doer.ID, issue.ID, form.Content)
if err != nil {
- if models.IsErrForbiddenIssueReaction(err) {
+ if issues_model.IsErrForbiddenIssueReaction(err) {
ctx.ServerError("ChangeIssueReaction", err)
return
}
log.Trace("Reaction for issue created: %d/%d/%d", ctx.Repo.Repository.ID, issue.ID, reaction.ID)
case "unreact":
- if err := models.DeleteIssueReaction(ctx.Doer, issue, form.Content); err != nil {
+ if err := issues_model.DeleteIssueReaction(ctx.Doer.ID, issue.ID, form.Content); err != nil {
ctx.ServerError("DeleteIssueReaction", err)
return
}
switch ctx.Params(":action") {
case "react":
- reaction, err := models.CreateCommentReaction(ctx.Doer, comment.Issue, comment, form.Content)
+ reaction, err := issues_model.CreateCommentReaction(ctx.Doer.ID, comment.Issue.ID, comment.ID, form.Content)
if err != nil {
- if models.IsErrForbiddenIssueReaction(err) {
+ if issues_model.IsErrForbiddenIssueReaction(err) {
ctx.ServerError("ChangeIssueReaction", err)
return
}
log.Trace("Reaction for comment created: %d/%d/%d/%d", ctx.Repo.Repository.ID, comment.Issue.ID, comment.ID, reaction.ID)
case "unreact":
- if err := models.DeleteCommentReaction(ctx.Doer, comment.Issue, comment, form.Content); err != nil {
+ if err := issues_model.DeleteCommentReaction(ctx.Doer.ID, comment.Issue.ID, comment.ID, form.Content); err != nil {
ctx.ServerError("DeleteCommentReaction", err)
return
}
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/foreignreference"
+ issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
}
// add reactions
for _, reaction := range issue.Reactions {
- res := models.Reaction{
+ res := issues_model.Reaction{
Type: reaction.Content,
CreatedUnix: timeutil.TimeStampNow(),
}
// add reactions
for _, reaction := range comment.Reactions {
- res := models.Reaction{
+ res := issues_model.Reaction{
Type: reaction.Content,
CreatedUnix: timeutil.TimeStampNow(),
}
// add reactions
for _, reaction := range pr.Reactions {
- res := models.Reaction{
+ res := issues_model.Reaction{
Type: reaction.Content,
CreatedUnix: timeutil.TimeStampNow(),
}