forked from gitea/gitea
		
	[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:
		
							parent
							
								
									ee7df7ba8c
								
							
						
					
					
						commit
						37e10d4543
					
				
							
								
								
									
										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()). | ||||
| 							Patch(mustNotBeArchived, bind(api.EditIssueCommentOption{}), repo.EditIssueComment). | ||||
| 							Delete(repo.DeleteIssueComment) | ||||
| 						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": { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 6543
						6543