mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-04 01:34:27 +00:00 
			
		
		
		
	[API] Add Reactions (#9220)
* reject reactions wich ar not allowed
* dont duble check CreateReaction now throw ErrForbiddenIssueReaction
* add /repos/{owner}/{repo}/issues/comments/{id}/reactions endpoint
* add Find Functions
* fix some swagger stuff + add issue reaction endpoints + GET ReactionList now use FindReactions...
* explicite Issue Only Reaction for FindReactionsOptions with "-1" commentID
* load issue; load user ...
* return error again
* swagger def canged after LINT
* check if user has ben loaded
* add Tests
* better way of comparing results
* add suggestion
* use different issue for test
(dont interfear with integration test)
* test dont compare Location on timeCompare
* TEST: add forbidden dubble add
* add comments in code to explain
* add settings.UI.ReactionsMap
so if !setting.UI.ReactionsMap[opts.Type] works
			
			
This commit is contained in:
		
							
								
								
									
										145
									
								
								integrations/api_issue_reaction_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								integrations/api_issue_reaction_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,145 @@
 | 
			
		||||
// Copyright 2019 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 integrations
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models"
 | 
			
		||||
	api "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestAPIIssuesReactions(t *testing.T) {
 | 
			
		||||
	defer prepareTestEnv(t)()
 | 
			
		||||
 | 
			
		||||
	issue := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 1}).(*models.Issue)
 | 
			
		||||
	_ = issue.LoadRepo()
 | 
			
		||||
	owner := models.AssertExistsAndLoadBean(t, &models.User{ID: issue.Repo.OwnerID}).(*models.User)
 | 
			
		||||
 | 
			
		||||
	session := loginUser(t, owner.Name)
 | 
			
		||||
	token := getTokenForLoggedInUser(t, session)
 | 
			
		||||
 | 
			
		||||
	user1 := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User)
 | 
			
		||||
	user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
 | 
			
		||||
	urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/reactions?token=%s",
 | 
			
		||||
		owner.Name, issue.Repo.Name, issue.Index, token)
 | 
			
		||||
 | 
			
		||||
	//Try to add not allowed reaction
 | 
			
		||||
	req := NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{
 | 
			
		||||
		Reaction: "wrong",
 | 
			
		||||
	})
 | 
			
		||||
	resp := session.MakeRequest(t, req, http.StatusForbidden)
 | 
			
		||||
 | 
			
		||||
	//Delete not allowed reaction
 | 
			
		||||
	req = NewRequestWithJSON(t, "DELETE", urlStr, &api.EditReactionOption{
 | 
			
		||||
		Reaction: "zzz",
 | 
			
		||||
	})
 | 
			
		||||
	resp = session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
 | 
			
		||||
	//Add allowed reaction
 | 
			
		||||
	req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{
 | 
			
		||||
		Reaction: "rocket",
 | 
			
		||||
	})
 | 
			
		||||
	resp = session.MakeRequest(t, req, http.StatusCreated)
 | 
			
		||||
	var apiNewReaction api.ReactionResponse
 | 
			
		||||
	DecodeJSON(t, resp, &apiNewReaction)
 | 
			
		||||
 | 
			
		||||
	//Add existing reaction
 | 
			
		||||
	resp = session.MakeRequest(t, req, http.StatusForbidden)
 | 
			
		||||
 | 
			
		||||
	//Get end result of reaction list of issue #1
 | 
			
		||||
	req = NewRequestf(t, "GET", urlStr)
 | 
			
		||||
	resp = session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
	var apiReactions []*api.ReactionResponse
 | 
			
		||||
	DecodeJSON(t, resp, &apiReactions)
 | 
			
		||||
	expectResponse := make(map[int]api.ReactionResponse)
 | 
			
		||||
	expectResponse[0] = api.ReactionResponse{
 | 
			
		||||
		User:     user1.APIFormat(),
 | 
			
		||||
		Reaction: "zzz",
 | 
			
		||||
		Created:  time.Unix(1573248002, 0),
 | 
			
		||||
	}
 | 
			
		||||
	expectResponse[1] = api.ReactionResponse{
 | 
			
		||||
		User:     user2.APIFormat(),
 | 
			
		||||
		Reaction: "eyes",
 | 
			
		||||
		Created:  time.Unix(1573248003, 0),
 | 
			
		||||
	}
 | 
			
		||||
	expectResponse[2] = apiNewReaction
 | 
			
		||||
	assert.Len(t, apiReactions, 3)
 | 
			
		||||
	for i, r := range apiReactions {
 | 
			
		||||
		assert.Equal(t, expectResponse[i].Reaction, r.Reaction)
 | 
			
		||||
		assert.Equal(t, expectResponse[i].Created.Unix(), r.Created.Unix())
 | 
			
		||||
		assert.Equal(t, expectResponse[i].User.ID, r.User.ID)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAPICommentReactions(t *testing.T) {
 | 
			
		||||
	defer prepareTestEnv(t)()
 | 
			
		||||
 | 
			
		||||
	comment := models.AssertExistsAndLoadBean(t, &models.Comment{ID: 2}).(*models.Comment)
 | 
			
		||||
	_ = comment.LoadIssue()
 | 
			
		||||
	issue := comment.Issue
 | 
			
		||||
	_ = issue.LoadRepo()
 | 
			
		||||
	owner := models.AssertExistsAndLoadBean(t, &models.User{ID: issue.Repo.OwnerID}).(*models.User)
 | 
			
		||||
 | 
			
		||||
	session := loginUser(t, owner.Name)
 | 
			
		||||
	token := getTokenForLoggedInUser(t, session)
 | 
			
		||||
 | 
			
		||||
	user1 := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User)
 | 
			
		||||
	user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
 | 
			
		||||
	urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/reactions?token=%s",
 | 
			
		||||
		owner.Name, issue.Repo.Name, comment.ID, token)
 | 
			
		||||
 | 
			
		||||
	//Try to add not allowed reaction
 | 
			
		||||
	req := NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{
 | 
			
		||||
		Reaction: "wrong",
 | 
			
		||||
	})
 | 
			
		||||
	resp := session.MakeRequest(t, req, http.StatusForbidden)
 | 
			
		||||
 | 
			
		||||
	//Delete none existing reaction
 | 
			
		||||
	req = NewRequestWithJSON(t, "DELETE", urlStr, &api.EditReactionOption{
 | 
			
		||||
		Reaction: "eyes",
 | 
			
		||||
	})
 | 
			
		||||
	resp = session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
 | 
			
		||||
	//Add allowed reaction
 | 
			
		||||
	req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{
 | 
			
		||||
		Reaction: "+1",
 | 
			
		||||
	})
 | 
			
		||||
	resp = session.MakeRequest(t, req, http.StatusCreated)
 | 
			
		||||
	var apiNewReaction api.ReactionResponse
 | 
			
		||||
	DecodeJSON(t, resp, &apiNewReaction)
 | 
			
		||||
 | 
			
		||||
	//Add existing reaction
 | 
			
		||||
	resp = session.MakeRequest(t, req, http.StatusForbidden)
 | 
			
		||||
 | 
			
		||||
	//Get end result of reaction list of issue #1
 | 
			
		||||
	req = NewRequestf(t, "GET", urlStr)
 | 
			
		||||
	resp = session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
	var apiReactions []*api.ReactionResponse
 | 
			
		||||
	DecodeJSON(t, resp, &apiReactions)
 | 
			
		||||
	expectResponse := make(map[int]api.ReactionResponse)
 | 
			
		||||
	expectResponse[0] = api.ReactionResponse{
 | 
			
		||||
		User:     user2.APIFormat(),
 | 
			
		||||
		Reaction: "laugh",
 | 
			
		||||
		Created:  time.Unix(1573248004, 0),
 | 
			
		||||
	}
 | 
			
		||||
	expectResponse[1] = api.ReactionResponse{
 | 
			
		||||
		User:     user1.APIFormat(),
 | 
			
		||||
		Reaction: "laugh",
 | 
			
		||||
		Created:  time.Unix(1573248005, 0),
 | 
			
		||||
	}
 | 
			
		||||
	expectResponse[2] = apiNewReaction
 | 
			
		||||
	assert.Len(t, apiReactions, 3)
 | 
			
		||||
	for i, r := range apiReactions {
 | 
			
		||||
		assert.Equal(t, expectResponse[i].Reaction, r.Reaction)
 | 
			
		||||
		assert.Equal(t, expectResponse[i].Created.Unix(), r.Created.Unix())
 | 
			
		||||
		assert.Equal(t, expectResponse[i].User.ID, r.User.ID)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1121,6 +1121,21 @@ func (err ErrNewIssueInsert) Error() string {
 | 
			
		||||
	return err.OriginalError.Error()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// __________      .__  .__ __________                                     __
 | 
			
		||||
// \______   \__ __|  | |  |\______   \ ____  ________ __   ____   _______/  |_
 | 
			
		||||
//  |     ___/  |  \  | |  | |       _// __ \/ ____/  |  \_/ __ \ /  ___/\   __\
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1,39 @@
 | 
			
		||||
[] # empty
 | 
			
		||||
-
 | 
			
		||||
  id: 1 #issue reaction
 | 
			
		||||
  type: zzz # not allowed reaction (added before allowed reaction list has changed)
 | 
			
		||||
  issue_id: 1
 | 
			
		||||
  comment_id: 0
 | 
			
		||||
  user_id: 2
 | 
			
		||||
  created_unix: 1573248001
 | 
			
		||||
 | 
			
		||||
-
 | 
			
		||||
  id: 2 #issue reaction
 | 
			
		||||
  type: zzz # not allowed reaction (added before allowed reaction list has changed)
 | 
			
		||||
  issue_id: 1
 | 
			
		||||
  comment_id: 0
 | 
			
		||||
  user_id: 1
 | 
			
		||||
  created_unix: 1573248002
 | 
			
		||||
 | 
			
		||||
-
 | 
			
		||||
  id: 3 #issue reaction
 | 
			
		||||
  type: eyes # allowed reaction
 | 
			
		||||
  issue_id: 1
 | 
			
		||||
  comment_id: 0
 | 
			
		||||
  user_id: 2
 | 
			
		||||
  created_unix: 1573248003
 | 
			
		||||
 | 
			
		||||
-
 | 
			
		||||
  id: 4 #comment reaction
 | 
			
		||||
  type: laugh # allowed reaction
 | 
			
		||||
  issue_id: 1
 | 
			
		||||
  comment_id: 2
 | 
			
		||||
  user_id: 2
 | 
			
		||||
  created_unix: 1573248004
 | 
			
		||||
 | 
			
		||||
-
 | 
			
		||||
  id: 5 #comment reaction
 | 
			
		||||
  type: laugh # allowed reaction
 | 
			
		||||
  issue_id: 1
 | 
			
		||||
  comment_id: 2
 | 
			
		||||
  user_id: 1
 | 
			
		||||
  created_unix: 1573248005
 | 
			
		||||
 
 | 
			
		||||
@@ -33,16 +33,38 @@ type FindReactionsOptions struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (opts *FindReactionsOptions) toConds() builder.Cond {
 | 
			
		||||
	//If Issue ID is set add to Query
 | 
			
		||||
	var 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})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return cond
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindCommentReactions returns a ReactionList of all reactions from an comment
 | 
			
		||||
func FindCommentReactions(comment *Comment) (ReactionList, error) {
 | 
			
		||||
	return findReactions(x, FindReactionsOptions{
 | 
			
		||||
		IssueID:   comment.IssueID,
 | 
			
		||||
		CommentID: comment.ID})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindIssueReactions returns a ReactionList of all reactions from an issue
 | 
			
		||||
func FindIssueReactions(issue *Issue) (ReactionList, error) {
 | 
			
		||||
	return findReactions(x, FindReactionsOptions{
 | 
			
		||||
		IssueID:   issue.ID,
 | 
			
		||||
		CommentID: -1,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func findReactions(e Engine, opts FindReactionsOptions) ([]*Reaction, error) {
 | 
			
		||||
	reactions := make([]*Reaction, 0, 10)
 | 
			
		||||
	sess := e.Where(opts.toConds())
 | 
			
		||||
@@ -77,6 +99,10 @@ type ReactionOptions struct {
 | 
			
		||||
 | 
			
		||||
// CreateReaction creates reaction for issue or comment.
 | 
			
		||||
func CreateReaction(opts *ReactionOptions) (reaction *Reaction, err error) {
 | 
			
		||||
	if !setting.UI.ReactionsMap[opts.Type] {
 | 
			
		||||
		return nil, ErrForbiddenIssueReaction{opts.Type}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sess := x.NewSession()
 | 
			
		||||
	defer sess.Close()
 | 
			
		||||
	if err = sess.Begin(); err != nil {
 | 
			
		||||
@@ -160,6 +186,19 @@ func DeleteCommentReaction(doer *User, issue *Issue, comment *Comment, content s
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadUser load user of reaction
 | 
			
		||||
func (r *Reaction) LoadUser() (*User, error) {
 | 
			
		||||
	if r.User != nil {
 | 
			
		||||
		return r.User, nil
 | 
			
		||||
	}
 | 
			
		||||
	user, err := getUserByID(x, r.UserID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	r.User = user
 | 
			
		||||
	return user, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReactionList represents list of reactions
 | 
			
		||||
type ReactionList []*Reaction
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -81,22 +81,22 @@ func TestIssueReactionCount(t *testing.T) {
 | 
			
		||||
	user4 := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User)
 | 
			
		||||
	ghost := NewGhostUser()
 | 
			
		||||
 | 
			
		||||
	issue1 := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)
 | 
			
		||||
	issue := AssertExistsAndLoadBean(t, &Issue{ID: 2}).(*Issue)
 | 
			
		||||
 | 
			
		||||
	addReaction(t, user1, issue1, nil, "heart")
 | 
			
		||||
	addReaction(t, user2, issue1, nil, "heart")
 | 
			
		||||
	addReaction(t, user3, issue1, nil, "heart")
 | 
			
		||||
	addReaction(t, user3, issue1, nil, "+1")
 | 
			
		||||
	addReaction(t, user4, issue1, nil, "+1")
 | 
			
		||||
	addReaction(t, user4, issue1, nil, "heart")
 | 
			
		||||
	addReaction(t, ghost, issue1, nil, "-1")
 | 
			
		||||
	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 := issue1.loadReactions(x)
 | 
			
		||||
	err := issue.loadReactions(x)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	assert.Len(t, issue1.Reactions, 7)
 | 
			
		||||
	assert.Len(t, issue.Reactions, 7)
 | 
			
		||||
 | 
			
		||||
	reactions := issue1.Reactions.GroupByType()
 | 
			
		||||
	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())
 | 
			
		||||
 
 | 
			
		||||
@@ -171,6 +171,7 @@ var (
 | 
			
		||||
		DefaultTheme          string
 | 
			
		||||
		Themes                []string
 | 
			
		||||
		Reactions             []string
 | 
			
		||||
		ReactionsMap          map[string]bool
 | 
			
		||||
		SearchRepoDescription bool
 | 
			
		||||
		UseServiceWorker      bool
 | 
			
		||||
 | 
			
		||||
@@ -985,6 +986,11 @@ func NewContext() {
 | 
			
		||||
	U2F.AppID = sec.Key("APP_ID").MustString(strings.TrimRight(AppURL, "/"))
 | 
			
		||||
 | 
			
		||||
	zip.Verbose = false
 | 
			
		||||
 | 
			
		||||
	UI.ReactionsMap = make(map[string]bool)
 | 
			
		||||
	for _, reaction := range UI.Reactions {
 | 
			
		||||
		UI.ReactionsMap[reaction] = true
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func loadInternalToken(sec *ini.Section) string {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										22
									
								
								modules/structs/issue_reaction.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								modules/structs/issue_reaction.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
// Copyright 2019 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 structs
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// EditReactionOption contain the reaction type
 | 
			
		||||
type EditReactionOption struct {
 | 
			
		||||
	Reaction string `json:"content"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReactionResponse contain one reaction
 | 
			
		||||
type ReactionResponse struct {
 | 
			
		||||
	User     *User  `json:"user"`
 | 
			
		||||
	Reaction string `json:"content"`
 | 
			
		||||
	// swagger:strfmt date-time
 | 
			
		||||
	Created time.Time `json:"created_at"`
 | 
			
		||||
}
 | 
			
		||||
@@ -657,21 +657,25 @@ func RegisterRoutes(m *macaron.Macaron) {
 | 
			
		||||
						Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueOption{}), repo.CreateIssue)
 | 
			
		||||
					m.Group("/comments", func() {
 | 
			
		||||
						m.Get("", repo.ListRepoIssueComments)
 | 
			
		||||
						m.Combo("/:id", reqToken()).
 | 
			
		||||
						m.Group("/:id", func() {
 | 
			
		||||
							m.Combo("", reqToken()).
 | 
			
		||||
								Patch(mustNotBeArchived, bind(api.EditIssueCommentOption{}), repo.EditIssueComment).
 | 
			
		||||
								Delete(repo.DeleteIssueComment)
 | 
			
		||||
							m.Combo("/reactions", reqToken()).
 | 
			
		||||
								Get(repo.GetIssueCommentReactions).
 | 
			
		||||
								Post(bind(api.EditReactionOption{}), repo.PostIssueCommentReaction).
 | 
			
		||||
								Delete(bind(api.EditReactionOption{}), repo.DeleteIssueCommentReaction)
 | 
			
		||||
						})
 | 
			
		||||
					})
 | 
			
		||||
					m.Group("/:index", func() {
 | 
			
		||||
						m.Combo("").Get(repo.GetIssue).
 | 
			
		||||
							Patch(reqToken(), bind(api.EditIssueOption{}), repo.EditIssue)
 | 
			
		||||
 | 
			
		||||
						m.Group("/comments", func() {
 | 
			
		||||
							m.Combo("").Get(repo.ListIssueComments).
 | 
			
		||||
								Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment)
 | 
			
		||||
							m.Combo("/:id", reqToken()).Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueCommentDeprecated).
 | 
			
		||||
								Delete(repo.DeleteIssueCommentDeprecated)
 | 
			
		||||
						})
 | 
			
		||||
 | 
			
		||||
						m.Group("/labels", func() {
 | 
			
		||||
							m.Combo("").Get(repo.ListIssueLabels).
 | 
			
		||||
								Post(reqToken(), bind(api.IssueLabelsOption{}), repo.AddIssueLabels).
 | 
			
		||||
@@ -679,12 +683,10 @@ func RegisterRoutes(m *macaron.Macaron) {
 | 
			
		||||
								Delete(reqToken(), repo.ClearIssueLabels)
 | 
			
		||||
							m.Delete("/:id", reqToken(), repo.DeleteIssueLabel)
 | 
			
		||||
						})
 | 
			
		||||
 | 
			
		||||
						m.Group("/times", func() {
 | 
			
		||||
							m.Combo("").Get(repo.ListTrackedTimes).
 | 
			
		||||
								Post(reqToken(), bind(api.AddTimeOption{}), repo.AddTime)
 | 
			
		||||
						})
 | 
			
		||||
 | 
			
		||||
						m.Combo("/deadline").Post(reqToken(), bind(api.EditDeadlineOption{}), repo.UpdateIssueDeadline)
 | 
			
		||||
						m.Group("/stopwatch", func() {
 | 
			
		||||
							m.Post("/start", reqToken(), repo.StartIssueStopwatch)
 | 
			
		||||
@@ -695,6 +697,10 @@ func RegisterRoutes(m *macaron.Macaron) {
 | 
			
		||||
							m.Put("/:user", reqToken(), repo.AddIssueSubscription)
 | 
			
		||||
							m.Delete("/:user", reqToken(), repo.DelIssueSubscription)
 | 
			
		||||
						})
 | 
			
		||||
						m.Combo("/reactions", reqToken()).
 | 
			
		||||
							Get(repo.GetIssueReactions).
 | 
			
		||||
							Post(bind(api.EditReactionOption{}), repo.PostIssueReaction).
 | 
			
		||||
							Delete(bind(api.EditReactionOption{}), repo.DeleteIssueReaction)
 | 
			
		||||
					})
 | 
			
		||||
				}, mustEnableIssuesOrPulls)
 | 
			
		||||
				m.Group("/labels", func() {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										394
									
								
								routers/api/v1/repo/issue_reaction.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										394
									
								
								routers/api/v1/repo/issue_reaction.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,394 @@
 | 
			
		||||
// Copyright 2019 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 repo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models"
 | 
			
		||||
	"code.gitea.io/gitea/modules/context"
 | 
			
		||||
	api "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GetIssueCommentReactions list reactions of a issue comment
 | 
			
		||||
func GetIssueCommentReactions(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation GET /repos/{owner}/{repo}/issues/comments/{id}/reactions issue issueGetCommentReactions
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Get a list reactions of a issue comment
 | 
			
		||||
	// consumes:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// produces:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// parameters:
 | 
			
		||||
	// - name: owner
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: owner of the repo
 | 
			
		||||
	//   type: string
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// - name: repo
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: name of the repo
 | 
			
		||||
	//   type: string
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// - name: id
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: id of the comment to edit
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	//   format: int64
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "200":
 | 
			
		||||
	//     "$ref": "#/responses/ReactionResponseList"
 | 
			
		||||
	comment, err := models.GetCommentByID(ctx.ParamsInt64(":id"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if models.IsErrCommentNotExist(err) {
 | 
			
		||||
			ctx.NotFound(err)
 | 
			
		||||
		} else {
 | 
			
		||||
			ctx.Error(500, "GetCommentByID", err)
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !ctx.Repo.CanRead(models.UnitTypeIssues) && !ctx.User.IsAdmin {
 | 
			
		||||
		ctx.Error(403, "GetIssueCommentReactions", errors.New("no permission to get reactions"))
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	reactions, err := models.FindCommentReactions(comment)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Error(500, "FindIssueReactions", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	_, err = reactions.LoadUsers()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Error(500, "ReactionList.LoadUsers()", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var result []api.ReactionResponse
 | 
			
		||||
	for _, r := range reactions {
 | 
			
		||||
		result = append(result, api.ReactionResponse{
 | 
			
		||||
			User:     r.User.APIFormat(),
 | 
			
		||||
			Reaction: r.Type,
 | 
			
		||||
			Created:  r.CreatedUnix.AsTime(),
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(200, result)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PostIssueCommentReaction add a reaction to a comment of a issue
 | 
			
		||||
func PostIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOption) {
 | 
			
		||||
	// swagger:operation POST /repos/{owner}/{repo}/issues/comments/{id}/reactions issue issuePostCommentReaction
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Add a reaction to a comment of a issue comment
 | 
			
		||||
	// consumes:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// produces:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// parameters:
 | 
			
		||||
	// - name: owner
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: owner of the repo
 | 
			
		||||
	//   type: string
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// - name: repo
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: name of the repo
 | 
			
		||||
	//   type: string
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// - name: id
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: id of the comment to edit
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	//   format: int64
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// - name: content
 | 
			
		||||
	//   in: body
 | 
			
		||||
	//   schema:
 | 
			
		||||
	//     "$ref": "#/definitions/EditReactionOption"
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "201":
 | 
			
		||||
	//     "$ref": "#/responses/ReactionResponse"
 | 
			
		||||
	changeIssueCommentReaction(ctx, form, true)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteIssueCommentReaction list reactions of a issue comment
 | 
			
		||||
func DeleteIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOption) {
 | 
			
		||||
	// swagger:operation DELETE /repos/{owner}/{repo}/issues/comments/{id}/reactions issue issueDeleteCommentReaction
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Remove a reaction from a comment of a issue comment
 | 
			
		||||
	// consumes:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// produces:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// parameters:
 | 
			
		||||
	// - name: owner
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: owner of the repo
 | 
			
		||||
	//   type: string
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// - name: repo
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: name of the repo
 | 
			
		||||
	//   type: string
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// - name: id
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: id of the comment to edit
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	//   format: int64
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// - name: content
 | 
			
		||||
	//   in: body
 | 
			
		||||
	//   schema:
 | 
			
		||||
	//     "$ref": "#/definitions/EditReactionOption"
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "200":
 | 
			
		||||
	//     "$ref": "#/responses/empty"
 | 
			
		||||
	changeIssueCommentReaction(ctx, form, false)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOption, isCreateType bool) {
 | 
			
		||||
	comment, err := models.GetCommentByID(ctx.ParamsInt64(":id"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if models.IsErrCommentNotExist(err) {
 | 
			
		||||
			ctx.NotFound(err)
 | 
			
		||||
		} else {
 | 
			
		||||
			ctx.Error(500, "GetCommentByID", err)
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = comment.LoadIssue()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Error(500, "comment.LoadIssue() failed", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if comment.Issue.IsLocked && !ctx.Repo.CanWrite(models.UnitTypeIssues) && !ctx.User.IsAdmin {
 | 
			
		||||
		ctx.Error(403, "ChangeIssueCommentReaction", errors.New("no permission to change reaction"))
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if isCreateType {
 | 
			
		||||
		// PostIssueCommentReaction part
 | 
			
		||||
		reaction, err := models.CreateCommentReaction(ctx.User, comment.Issue, comment, form.Reaction)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if models.IsErrForbiddenIssueReaction(err) {
 | 
			
		||||
				ctx.Error(403, err.Error(), err)
 | 
			
		||||
			} else {
 | 
			
		||||
				ctx.Error(500, "CreateCommentReaction", err)
 | 
			
		||||
			}
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		_, err = reaction.LoadUser()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.Error(500, "Reaction.LoadUser()", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ctx.JSON(201, api.ReactionResponse{
 | 
			
		||||
			User:     reaction.User.APIFormat(),
 | 
			
		||||
			Reaction: reaction.Type,
 | 
			
		||||
			Created:  reaction.CreatedUnix.AsTime(),
 | 
			
		||||
		})
 | 
			
		||||
	} else {
 | 
			
		||||
		// DeleteIssueCommentReaction part
 | 
			
		||||
		err = models.DeleteCommentReaction(ctx.User, comment.Issue, comment, form.Reaction)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.Error(500, "DeleteCommentReaction", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		ctx.Status(200)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetIssueReactions list reactions of a issue comment
 | 
			
		||||
func GetIssueReactions(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation GET /repos/{owner}/{repo}/issues/{index}/reactions issue issueGetIssueReactions
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Get a list reactions of a issue
 | 
			
		||||
	// consumes:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// produces:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// parameters:
 | 
			
		||||
	// - name: owner
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: owner of the repo
 | 
			
		||||
	//   type: string
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// - name: repo
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: name of the repo
 | 
			
		||||
	//   type: string
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// - name: index
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: index of the issue
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	//   format: int64
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "200":
 | 
			
		||||
	//     "$ref": "#/responses/ReactionResponseList"
 | 
			
		||||
	issue, err := models.GetIssueWithAttrsByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if models.IsErrIssueNotExist(err) {
 | 
			
		||||
			ctx.NotFound()
 | 
			
		||||
		} else {
 | 
			
		||||
			ctx.Error(500, "GetIssueByIndex", err)
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !ctx.Repo.CanRead(models.UnitTypeIssues) && !ctx.User.IsAdmin {
 | 
			
		||||
		ctx.Error(403, "GetIssueReactions", errors.New("no permission to get reactions"))
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	reactions, err := models.FindIssueReactions(issue)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Error(500, "FindIssueReactions", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	_, err = reactions.LoadUsers()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Error(500, "ReactionList.LoadUsers()", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var result []api.ReactionResponse
 | 
			
		||||
	for _, r := range reactions {
 | 
			
		||||
		result = append(result, api.ReactionResponse{
 | 
			
		||||
			User:     r.User.APIFormat(),
 | 
			
		||||
			Reaction: r.Type,
 | 
			
		||||
			Created:  r.CreatedUnix.AsTime(),
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(200, result)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PostIssueReaction add a reaction to a comment of a issue
 | 
			
		||||
func PostIssueReaction(ctx *context.APIContext, form api.EditReactionOption) {
 | 
			
		||||
	// swagger:operation POST /repos/{owner}/{repo}/issues/{index}/reactions issue issuePostIssueReaction
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Add a reaction to a comment of a issue
 | 
			
		||||
	// consumes:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// produces:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// parameters:
 | 
			
		||||
	// - name: owner
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: owner of the repo
 | 
			
		||||
	//   type: string
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// - name: repo
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: name of the repo
 | 
			
		||||
	//   type: string
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// - name: index
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: index of the issue
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	//   format: int64
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// - name: content
 | 
			
		||||
	//   in: body
 | 
			
		||||
	//   schema:
 | 
			
		||||
	//     "$ref": "#/definitions/EditReactionOption"
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "201":
 | 
			
		||||
	//     "$ref": "#/responses/ReactionResponse"
 | 
			
		||||
	changeIssueReaction(ctx, form, true)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteIssueReaction list reactions of a issue comment
 | 
			
		||||
func DeleteIssueReaction(ctx *context.APIContext, form api.EditReactionOption) {
 | 
			
		||||
	// swagger:operation DELETE /repos/{owner}/{repo}/issues/{index}/reactions issue issueDeleteIssueReaction
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Remove a reaction from a comment of a issue
 | 
			
		||||
	// consumes:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// produces:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// parameters:
 | 
			
		||||
	// - name: owner
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: owner of the repo
 | 
			
		||||
	//   type: string
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// - name: repo
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: name of the repo
 | 
			
		||||
	//   type: string
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// - name: index
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: index of the issue
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	//   format: int64
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// - name: content
 | 
			
		||||
	//   in: body
 | 
			
		||||
	//   schema:
 | 
			
		||||
	//     "$ref": "#/definitions/EditReactionOption"
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "200":
 | 
			
		||||
	//     "$ref": "#/responses/empty"
 | 
			
		||||
	changeIssueReaction(ctx, form, false)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func changeIssueReaction(ctx *context.APIContext, form api.EditReactionOption, isCreateType bool) {
 | 
			
		||||
	issue, err := models.GetIssueWithAttrsByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if models.IsErrIssueNotExist(err) {
 | 
			
		||||
			ctx.NotFound()
 | 
			
		||||
		} else {
 | 
			
		||||
			ctx.Error(500, "GetIssueByIndex", err)
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if issue.IsLocked && !ctx.Repo.CanWrite(models.UnitTypeIssues) && !ctx.User.IsAdmin {
 | 
			
		||||
		ctx.Error(403, "ChangeIssueCommentReaction", errors.New("no permission to change reaction"))
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if isCreateType {
 | 
			
		||||
		// PostIssueReaction part
 | 
			
		||||
		reaction, err := models.CreateIssueReaction(ctx.User, issue, form.Reaction)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if models.IsErrForbiddenIssueReaction(err) {
 | 
			
		||||
				ctx.Error(403, err.Error(), err)
 | 
			
		||||
			} else {
 | 
			
		||||
				ctx.Error(500, "CreateCommentReaction", err)
 | 
			
		||||
			}
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		_, err = reaction.LoadUser()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.Error(500, "Reaction.LoadUser()", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ctx.JSON(201, api.ReactionResponse{
 | 
			
		||||
			User:     reaction.User.APIFormat(),
 | 
			
		||||
			Reaction: reaction.Type,
 | 
			
		||||
			Created:  reaction.CreatedUnix.AsTime(),
 | 
			
		||||
		})
 | 
			
		||||
	} else {
 | 
			
		||||
		// DeleteIssueReaction part
 | 
			
		||||
		err = models.DeleteIssueReaction(ctx.User, issue, form.Reaction)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.Error(500, "DeleteIssueReaction", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		ctx.Status(200)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -84,3 +84,24 @@ type swaggerIssueDeadline struct {
 | 
			
		||||
	// in:body
 | 
			
		||||
	Body api.IssueDeadline `json:"body"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EditReactionOption
 | 
			
		||||
// swagger:response EditReactionOption
 | 
			
		||||
type swaggerEditReactionOption struct {
 | 
			
		||||
	// in:body
 | 
			
		||||
	Body api.EditReactionOption `json:"body"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReactionResponse
 | 
			
		||||
// swagger:response ReactionResponse
 | 
			
		||||
type swaggerReactionResponse struct {
 | 
			
		||||
	// in:body
 | 
			
		||||
	Body api.ReactionResponse `json:"body"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReactionResponseList
 | 
			
		||||
// swagger:response ReactionResponseList
 | 
			
		||||
type swaggerReactionResponseList struct {
 | 
			
		||||
	// in:body
 | 
			
		||||
	Body []api.ReactionResponse `json:"body"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1463,14 +1463,12 @@ func ChangeIssueReaction(ctx *context.Context, form auth.ReactionForm) {
 | 
			
		||||
 | 
			
		||||
	switch ctx.Params(":action") {
 | 
			
		||||
	case "react":
 | 
			
		||||
		if !util.IsStringInSlice(form.Content, setting.UI.Reactions) {
 | 
			
		||||
			err := fmt.Errorf("ChangeIssueReaction: '%s' is not an allowed reaction", form.Content)
 | 
			
		||||
			ctx.ServerError(err.Error(), err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		reaction, err := models.CreateIssueReaction(ctx.User, issue, form.Content)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if models.IsErrForbiddenIssueReaction(err) {
 | 
			
		||||
				ctx.ServerError("ChangeIssueReaction", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			log.Info("CreateIssueReaction: %s", err)
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
@@ -1564,14 +1562,12 @@ func ChangeCommentReaction(ctx *context.Context, form auth.ReactionForm) {
 | 
			
		||||
 | 
			
		||||
	switch ctx.Params(":action") {
 | 
			
		||||
	case "react":
 | 
			
		||||
		if !util.IsStringInSlice(form.Content, setting.UI.Reactions) {
 | 
			
		||||
			err := fmt.Errorf("ChangeIssueReaction: '%s' is not an allowed reaction", form.Content)
 | 
			
		||||
			ctx.ServerError(err.Error(), err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		reaction, err := models.CreateCommentReaction(ctx.User, comment.Issue, comment, form.Content)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if models.IsErrForbiddenIssueReaction(err) {
 | 
			
		||||
				ctx.ServerError("ChangeIssueReaction", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			log.Info("CreateCommentReaction: %s", err)
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -3016,6 +3016,148 @@
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/repos/{owner}/{repo}/issues/comments/{id}/reactions": {
 | 
			
		||||
      "get": {
 | 
			
		||||
        "consumes": [
 | 
			
		||||
          "application/json"
 | 
			
		||||
        ],
 | 
			
		||||
        "produces": [
 | 
			
		||||
          "application/json"
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "issue"
 | 
			
		||||
        ],
 | 
			
		||||
        "summary": "Get a list reactions of a issue comment",
 | 
			
		||||
        "operationId": "issueGetCommentReactions",
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "owner of the repo",
 | 
			
		||||
            "name": "owner",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "name of the repo",
 | 
			
		||||
            "name": "repo",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "integer",
 | 
			
		||||
            "format": "int64",
 | 
			
		||||
            "description": "id of the comment to edit",
 | 
			
		||||
            "name": "id",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "200": {
 | 
			
		||||
            "$ref": "#/responses/ReactionResponseList"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "post": {
 | 
			
		||||
        "consumes": [
 | 
			
		||||
          "application/json"
 | 
			
		||||
        ],
 | 
			
		||||
        "produces": [
 | 
			
		||||
          "application/json"
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "issue"
 | 
			
		||||
        ],
 | 
			
		||||
        "summary": "Add a reaction to a comment of a issue comment",
 | 
			
		||||
        "operationId": "issuePostCommentReaction",
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "owner of the repo",
 | 
			
		||||
            "name": "owner",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "name of the repo",
 | 
			
		||||
            "name": "repo",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "integer",
 | 
			
		||||
            "format": "int64",
 | 
			
		||||
            "description": "id of the comment to edit",
 | 
			
		||||
            "name": "id",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "name": "content",
 | 
			
		||||
            "in": "body",
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "$ref": "#/definitions/EditReactionOption"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "201": {
 | 
			
		||||
            "$ref": "#/responses/ReactionResponse"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "delete": {
 | 
			
		||||
        "consumes": [
 | 
			
		||||
          "application/json"
 | 
			
		||||
        ],
 | 
			
		||||
        "produces": [
 | 
			
		||||
          "application/json"
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "issue"
 | 
			
		||||
        ],
 | 
			
		||||
        "summary": "Remove a reaction from a comment of a issue comment",
 | 
			
		||||
        "operationId": "issueDeleteCommentReaction",
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "owner of the repo",
 | 
			
		||||
            "name": "owner",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "name of the repo",
 | 
			
		||||
            "name": "repo",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "integer",
 | 
			
		||||
            "format": "int64",
 | 
			
		||||
            "description": "id of the comment to edit",
 | 
			
		||||
            "name": "id",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "name": "content",
 | 
			
		||||
            "in": "body",
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "$ref": "#/definitions/EditReactionOption"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "200": {
 | 
			
		||||
            "$ref": "#/responses/empty"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/repos/{owner}/{repo}/issues/{id}/times": {
 | 
			
		||||
      "get": {
 | 
			
		||||
        "produces": [
 | 
			
		||||
@@ -3688,6 +3830,148 @@
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/repos/{owner}/{repo}/issues/{index}/reactions": {
 | 
			
		||||
      "get": {
 | 
			
		||||
        "consumes": [
 | 
			
		||||
          "application/json"
 | 
			
		||||
        ],
 | 
			
		||||
        "produces": [
 | 
			
		||||
          "application/json"
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "issue"
 | 
			
		||||
        ],
 | 
			
		||||
        "summary": "Get a list reactions of a issue",
 | 
			
		||||
        "operationId": "issueGetIssueReactions",
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "owner of the repo",
 | 
			
		||||
            "name": "owner",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "name of the repo",
 | 
			
		||||
            "name": "repo",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "integer",
 | 
			
		||||
            "format": "int64",
 | 
			
		||||
            "description": "index of the issue",
 | 
			
		||||
            "name": "index",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "200": {
 | 
			
		||||
            "$ref": "#/responses/ReactionResponseList"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "post": {
 | 
			
		||||
        "consumes": [
 | 
			
		||||
          "application/json"
 | 
			
		||||
        ],
 | 
			
		||||
        "produces": [
 | 
			
		||||
          "application/json"
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "issue"
 | 
			
		||||
        ],
 | 
			
		||||
        "summary": "Add a reaction to a comment of a issue",
 | 
			
		||||
        "operationId": "issuePostIssueReaction",
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "owner of the repo",
 | 
			
		||||
            "name": "owner",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "name of the repo",
 | 
			
		||||
            "name": "repo",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "integer",
 | 
			
		||||
            "format": "int64",
 | 
			
		||||
            "description": "index of the issue",
 | 
			
		||||
            "name": "index",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "name": "content",
 | 
			
		||||
            "in": "body",
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "$ref": "#/definitions/EditReactionOption"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "201": {
 | 
			
		||||
            "$ref": "#/responses/ReactionResponse"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "delete": {
 | 
			
		||||
        "consumes": [
 | 
			
		||||
          "application/json"
 | 
			
		||||
        ],
 | 
			
		||||
        "produces": [
 | 
			
		||||
          "application/json"
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "issue"
 | 
			
		||||
        ],
 | 
			
		||||
        "summary": "Remove a reaction from a comment of a issue",
 | 
			
		||||
        "operationId": "issueDeleteIssueReaction",
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "owner of the repo",
 | 
			
		||||
            "name": "owner",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "name of the repo",
 | 
			
		||||
            "name": "repo",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "integer",
 | 
			
		||||
            "format": "int64",
 | 
			
		||||
            "description": "index of the issue",
 | 
			
		||||
            "name": "index",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "name": "content",
 | 
			
		||||
            "in": "body",
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "$ref": "#/definitions/EditReactionOption"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "200": {
 | 
			
		||||
            "$ref": "#/responses/empty"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/repos/{owner}/{repo}/issues/{index}/stopwatch/start": {
 | 
			
		||||
      "post": {
 | 
			
		||||
        "consumes": [
 | 
			
		||||
@@ -8721,6 +9005,17 @@
 | 
			
		||||
      },
 | 
			
		||||
      "x-go-package": "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
    },
 | 
			
		||||
    "EditReactionOption": {
 | 
			
		||||
      "description": "EditReactionOption contain the reaction type",
 | 
			
		||||
      "type": "object",
 | 
			
		||||
      "properties": {
 | 
			
		||||
        "content": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "x-go-name": "Reaction"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "x-go-package": "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
    },
 | 
			
		||||
    "EditReleaseOption": {
 | 
			
		||||
      "description": "EditReleaseOption options when editing a release",
 | 
			
		||||
      "type": "object",
 | 
			
		||||
@@ -10095,6 +10390,25 @@
 | 
			
		||||
      },
 | 
			
		||||
      "x-go-package": "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
    },
 | 
			
		||||
    "ReactionResponse": {
 | 
			
		||||
      "description": "ReactionResponse contain one reaction",
 | 
			
		||||
      "type": "object",
 | 
			
		||||
      "properties": {
 | 
			
		||||
        "content": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "x-go-name": "Reaction"
 | 
			
		||||
        },
 | 
			
		||||
        "created_at": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "format": "date-time",
 | 
			
		||||
          "x-go-name": "Created"
 | 
			
		||||
        },
 | 
			
		||||
        "user": {
 | 
			
		||||
          "$ref": "#/definitions/User"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "x-go-package": "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
    },
 | 
			
		||||
    "Reference": {
 | 
			
		||||
      "type": "object",
 | 
			
		||||
      "title": "Reference represents a Git reference.",
 | 
			
		||||
@@ -10960,6 +11274,12 @@
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "EditReactionOption": {
 | 
			
		||||
      "description": "EditReactionOption",
 | 
			
		||||
      "schema": {
 | 
			
		||||
        "$ref": "#/definitions/EditReactionOption"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "EmailList": {
 | 
			
		||||
      "description": "EmailList",
 | 
			
		||||
      "schema": {
 | 
			
		||||
@@ -11146,6 +11466,21 @@
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "ReactionResponse": {
 | 
			
		||||
      "description": "ReactionResponse",
 | 
			
		||||
      "schema": {
 | 
			
		||||
        "$ref": "#/definitions/ReactionResponse"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "ReactionResponseList": {
 | 
			
		||||
      "description": "ReactionResponseList",
 | 
			
		||||
      "schema": {
 | 
			
		||||
        "type": "array",
 | 
			
		||||
        "items": {
 | 
			
		||||
          "$ref": "#/definitions/ReactionResponse"
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "Reference": {
 | 
			
		||||
      "description": "Reference",
 | 
			
		||||
      "schema": {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user